【JVM】一篇通关JVM垃圾回收
目录
- 1. 如何判断对象可以回收
- 1-1. 引用计数法
- 1-2. 可达性分析算法
- 1-3. 四种引用
- 强引用
- 软引用
- 弱引用
- 虚引用
- 终结器引用
- 2. 垃圾回收算法
- 2-1. 标记清除
- 2-2. 标记整理
- 2-3. 复制
- 2-4. 总结
- 3. 分代垃圾回收
- 3-1. 回收流程
- 3-2. VM参数分析
- VM参数
- 4. 垃圾回收器
- 5. 垃圾回收调优
1. 如何判断对象可以回收
1-1. 引用计数法
引用计数法
- 只要一个对象被其他变量所引用,那么就让这个对象的计数
+1
- 如果其他变量不再引用,让这个对象的计数
-1
- 让这个对象的引用计数为
0
时,则说明没有变量引用了,就可以作为一个垃圾进行回收
引用计数法弊端
- 循环引用问题,A对象与B对象循环引用,他们的引用计数始终为1,不能作为一个垃圾进行回收
- 出现内存泄漏
1-2. 可达性分析算法
根对象:一些肯定不能作为垃圾的对象
可达性分析算法
-
在垃圾回收之前,会对堆内存所有的对象进行扫描
-
查看每一个对象是不是被
根对象直接或间接引用
-
如果是,则不能被垃圾回收
-
如果不是,则可以被垃圾回收
-
Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象
-
扫描堆中的对象,看是否能够沿着GC Root对象
(堆中对象)
为起点的引用链找到该对象,找不到,表示堆中对象
可以回收 -
哪些对象可以作为GC Root
- 活动线程中局部变量
所引用的堆中对象
可以作为根对象
- 活动线程中局部变量
1-3. 四种引用
实线代表强引用
平时用的引用都是强引用
,例如:赋值运算
强引用
- 所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。
- Object obj = new Object(); //只要obj还指向Object对象,Object对象就不会被回收 obj = null; //手动置null后会被回收
软引用
- 只有【软引用】引用该对象时,在垃圾回收后,内存仍不足 则会回收软引用对象
- 可以配合【引用队列】来释放软引用自身,因为软引用自身也占用内存
- 在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
★应用场景
- 非核心业务资源(比如:图片)被强引用特别多时,有可能报OOM异常,因为强引用是不会被回收的,内存满直接抛异常
- 那么就可以用【软引用】来指向这些资源,当内存不足时,回收这些资源。
- 以后如果再使用图片资源,重新读取一遍。
- 这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。
- 在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
弱引用
- 只有【弱引用】引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
- 可以配合【引用队列】来释放弱引用自身,因为弱引用自身也占用内存
- 在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
虚引用
- 必须配合【引用队列】使用
- 例如
ByteBuffer
对象不再【强引用】时,ByteBuffer
对象本身可以被垃圾回收,但是占用的直接内存
是属于操作系统的,无法被回收。 - 那么就可以将【虚引用】放入【引用队列】, 由
Reference Handler
线程调用虚引用相关方法释放【直接内存】 - 如上图,B对象不再引用
ByteBuffer
对象,ByteBuffer
就会被回收。但是直接内存中的内存还未被回收。这时需要将虚引用对象Cleaner
放入引用队列中,然后调用它的clean
方法来释放直接内存 - 总结:【虚引用】引用的对象被垃圾回收时,【虚引用】被放入【引用队列】,从而由一个线程可以调用【虚引用】对象中的方法执行一系列操作。
- 在 JDK1.2 之后,用 PhantomReference 类来表示
终结器引用
Object
类中有finallize()
方法- 当一个类重写了
Object
类中有finallize()
方法,并且该对象没有被【强引用】时,就可以进行垃圾回收 - 第一次垃圾回收时,将【终结器引用】放入【引用队列】,并且由一个优先级很低的Finalizer线程去寻找【终结器引用】的对象,找到后执行该对象的
finallize()
方法。 - 直到第二次垃圾回收时,才将该对象进行垃圾回收。
软引用使用:
public class Demo1 {
public static void main(String[] args) {
final int _4M = 4*1024*1024;
//list是强引用,byte数组是软引用
List<SoftReference<byte[]>> list = new ArrayList<>();
SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M]);
}
}
软引用配合引用队列使用:
public static void main(String[] args) throws IOException {
///使用引用队列,用于移除引用为空的软引用
ReferenceQueue<byte[]> queue=new ReferenceQueue<>();
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
//关联了引用队列,当软引用所关联的byte数组被回收时,软引用自己就会加入到引用队列queue中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB],queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
//获取队列中第一个软引用
Reference<? extends byte[]> poll = queue.poll();
//遍历引用队列,如果有软引用,则移除
while(poll!=null){
list.remove(poll);
poll=queue.poll();
}
System.out.println("=============");
System.out.println("循环结束:" + list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
弱引用使用:
弱引用的使用和软引用类似,只是将 SoftReference
换为了 WeakReference
public static void main(String[] args) {
//list是强引用,byte数组是弱引用
List<WeakReference<byte[]>> list=new ArrayList<>();
for (int i = 0; i < 5; i++) {
WeakReference<byte[]> ref=new WeakReference<>(new byte[_4MB]);
list.add(ref);
for (WeakReference<byte[]> w : list) {
System.out.print(w.get()+" ");
}
System.out.println();
}
System.out.println("循环结束:"+list.size());
}
2. 垃圾回收算法
2-1. 标记清除
标记清除
在垃圾回收的过程中
- 标记:确定哪些对象是可回收对象,
- 清除:标记好之后,清除可回收对象的内存,
并不是将内存空间字节清零
,而是记录内存起始地址。
注意:这里的清除并不是将内存空间字节清零,而是记录这段内存的起始地址,下次分配内存的时候,会直接覆盖这段内存。
优点: 速度快
缺点: 容易产生内存碎片。一旦分配较大内存的对象,由于内存不连续,导致无法分配,最后就会造成内存溢出问题
2-2. 标记整理
标记整理
- 先采用标记算法确定可回收对象
- 然后整理剩余的对象内存,将可用的对象内存移动到一起,使内存更加紧凑,连续的空间就更多。
优点:不会有内存碎片
缺点:速度慢
2-3. 复制
复制算法
- 将内存分为等大小的两个区域,FROM和TO(TO中为空)。
- 将被GC Root引用的对象从FROM放入TO中,然后回收不被GC Root引用的对象。
- 回收完之后交换FROM和TO两个区域。这样也可以避免内存碎片的问题,但是会占用双倍的内存空间。
优点:不会有内存碎片
缺点:会占用双倍的内存空间。速度慢
2-4. 总结
- JVM会根据不同的情况来采用这3种算法
- 不会只使用一种算法
3. 分代垃圾回收
堆内存分为新生代和老年代,新生代有划分为伊甸园,幸存区To,幸存区From。
新生代
- 主要存放刚创建的对象
老年代
- 主要存放长时间使用的对象
新生代和老年代会进行不同的垃圾回收算法
3-1. 回收流程
新创建的对象分配在伊甸园
如果新生代空间不足时,触发 Minor GC(minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行)回收伊甸园
和 幸存区
中的垃圾,伊甸园
和 from
存活的对象使用 copy 复制到 to
中,存活的对象年龄加 1并且交换 from
和 to
,并让幸存区from
中的对象寿命加1
当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit) 不同的垃圾回收器阈值会不同
如果老年代空间不足,会先尝试触发Minor GC,回收新生代的垃圾。如果之后空间仍不足,那么触发 Full GC,回收新生代和老年代的垃圾,stop the world的时间更长
如果 Full GC之后,空间仍不足。则触发OutOfMemory - Java heap space0000000
3-2. VM参数分析
VM参数
含义 | 参数 |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) |
幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
幸存区比例 | -XX:SurvivorRatio=ratio |
晋升阈值 | -XX:MaxTenuringThreshold=threshold |
晋升详情 | -XX:+PrintTenuringDistribution |
GC详情 | -XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC | XX:+ScavengeBeforeFullGC |