JVM结构
Java8 JVM内存结构
基本结构与之前类似,只是Java8取消了之前的“永久代”,取而代之的是“元空间”——Metaspace,两者本质是一样的。“永久代”使用的是JVM的堆内存,而“元空间”是直接使用的本机物理内存。
简单来说就是栈管运行,而堆管存储
GC Roots
如果判断一个对象可以被回收?
引用计数算法
维护一个计数器,如果有对该对象的引用,计数器+1,反之-1。无法解决循环引用的问题。
可达性分析算法
从一组名为“GC Roots”的根节点对象出发,向下遍历。那些没有被遍历到、与GC Roots形成通路的对象,会被标记为“回收”。
哪些对象可以作为GC Roots?
- 虚拟机栈(栈帧中的局部变量)中引用的对象。
- 本地方法栈(native)中引用的对象。
- 方法区中常量引用的对象。
- 方法区中类静态属性引用的对象。
JVM参数
JVM 三种类型参数
标配参数
比如-version
、-help
、-showversion
等,几乎不会改变。
X参数
用得不多,比如-Xint
,解释执行模式;-Xcomp
,编译模式;-Xmixed
,开启混合模式(默认)。
XX参数
重要,用于JVM调优。
JVM XX参数
布尔类型
公式:-XX:+某个属性
、-XX:-某个属性
,开启或关闭某个功能。
比如-XX:+PrintGCDetails
,开启GC详细信息。
KV键值类型
公式:-XX:属性key=值value
。比如-XX:Metaspace=128m
、
-XX:MaxTenuringThreshold=15
。
JVM Xms/Xmx参数
-Xms
和-Xmx
十分常见,用于设置初始堆大小和最大堆大小。第一眼看上去,既不像X参数,也不像XX参数。实际上-Xms
等价于-XX:InitialHeapSize
,-Xmx
等价于-XX:MaxHeapSize
。所以-Xms
和-Xmx
属于XX参数。
JVM 查看参数
查看某个参数
使用jps -l
配合jinfo -flag JVM参数 pid
。先用jsp -l
查看java进程,选择某个进程号。
jinfo -flag PrintGCDetails 18052
可以查看18052 Java进程的PrintGCDetails
参数信息。
公式:-XX:+某个属性
、-XX:-某个属性
,开启或关闭某个功能。
比如-XX:+PrintGCDetails
,开启GC详细信息。
查看所有参数
使用jps -l
配合jinfo -flags pid
可以查看所有参数。
也可以使用java -XX:+PrintFlagsInitial
查看修改后的参数
使用java -XX:PrintFlagsFinal
可以查看修改后的参数,与上面类似。只是修改过后是:=
而不是=
。
查看常见参数
如果不想查看所有参数,可以用-XX:+PrintCommandLineFlags
查看常用参数。
JVM 常用参数
-Xmx/-Xms
最大和初始堆大小。最大默认为物理内存的1/4,初始默认为物理内存的1/64。
-Xss
等价于-XX:ThresholdStackSize
。用于设置单个栈的大小,系统默认值是0,不代表栈大小为0。而是根据操作系统的不同,有不同的值。比如64位的Linux系统是1024K,而Windows系统依赖于虚拟内存。
-Xmn
新生代大小,一般不调。
-XX:MetaspaceSize
设置元空间大小。
-XX:+PrintGCDetails
输出GC收集信息,包含GC
和Full GC
信息。
-XX:SurvivorRatio
新生代中,Eden
区和两个Survivor
区的比例,默认是8:1:1
。通过-XX:SurvivorRatio=4
改成4:1:1
-XX:NewRatio
老生代和新年代的比列,默认是2,即老年代占2,新生代占1。如果改成-XX:NewRatio=4
,则老年代占4,新生代占1。
-XX:MaxTenuringThreshold
新生代设置进入老年代的时间,默认是新生代逃过15次GC后,进入老年代。如果改成0,那么对象不会在新生代分配,直接进入老年代。
四大引用
强引用
使用new
方法创造出来的对象,默认都是强引用。GC的时候,就算内存不够,抛出OutOfMemoryError
也不会回收对象,死了也不回收。我们一般创建对象都是强引用
软引用
需要用Object.Reference.SoftReference
来显示创建。如果内存够,GC的时候不回收。内存不够,则回收。常用于内存敏感的应用,比如高速缓存。
package jvm;
import java.lang.ref.SoftReference;
public class SoftReferenceDemo {
public static void main(String[] args) {
softRef_Memory_Enough();
System.out.println("Not Enough");
softRef_Memory_NotEnough();
}
private static void softRef_Memory_Enough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
System.out.println("===========");
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
private static void softRef_Memory_NotEnough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
System.out.println("===========");
o1 = null;
System.gc();
try {
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(o1);
System.out.println(softReference.get());
}
}
}
弱引用
需要用Object.Reference.WeakReference
来显示创建。无论内存够不够,GC的时候都回收,也可以用在高速缓存上。
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<>(o1);
System.out.println(o1);
System.out.println(weakReference.get());
System.out.println("==========");
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(weakReference.get());
}
WeakHashMap
传统的HashMap
就算key==null
了,也不会回收键值对。但是如果是WeakHashMap
,一旦内存不够用时,且key==null
时,会回收这个键值对。
public static void main(String[] args) {
byte[] bytes = new byte[30 * 1024 * 1024];
myHashMap();
System.out.println("===============");
myWeakHashMap();
}
private static void myHashMap() {
HashMap<Integer, String> map = new HashMap<>();
Integer key = 1;
String value = "HashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.out.println(map);
System.gc();
System.out.println(map + "\t" + map.size());
}
private static void myWeakHashMap() {
WeakHashMap<Integer, String> map = new WeakHashMap<>();
Integer key = 2;
String value = "WeakHashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.out.println(map);
System.gc();
System.out.println(map + "\t" + map.size());
}
虚引用
软应用和弱引用可以通过get()
方法获得对象,但是虚引用不行。虚引形同虚设,在任何时候都可能被GC,不能单独使用,必须配合引用队列(ReferenceQueue)来使用。设置虚引用的唯一目的,就是在这个对象被回收时,收到一个通知以便进行后续操作,有点像Spring
的后置通知。
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference phantomReference = new PhantomReference(o1, referenceQueue);
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
System.out.println("===========");
o1 = null;
System.gc();
Thread.sleep(500);
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
}
引用队列
弱引用、虚引用被回收后,会被放到引用队列里面,通过poll
方法可以得到。关于引用队列和弱、虚引用的配合使用,