垃圾标记与收集算法

本文主要是以通俗易懂的画图方式解释了标记清除算法和可达性分析算法,以及常用的回收算法(标记清除、标记整理、复制算法)以及整合百家之长的分代回收算法,另外还介绍了触发 Full GC 的几个场景。

标记算法

1、引用计数法

回收对象之前得判断那些是需要被清除的对象,首先出现的是引用计数算法,判断对象的引用数量,通过判断对象的引用数量来决定对象是否可以被回收,每个对象实例都有一个引用计数器,被引用则 + 1,完成引用则 - 1,任何引用计数为 0 的对象实例可以被当作垃圾收集。

优点:执行效率高,程序执行受影响较小

缺点:无法检测出循环引用的情况,导致内存泄露

1
2
3
4
5
6
7
8
9
10
public class MyObject{
MyObject object;

public static void main(String [] args) {
MyObject myObject = new MyObject();
MyObject myObject2 = new MyObject();
myObject.object = myObject2;
myObject2.object = myObject;
}
}

2、可达性分析算法

通过判断对象的引用链是否可达来决定对象是否可以被回收

mark

可以作为 GC Root 的对象

1、虚拟机栈中引用的对象 (栈帧中的本地变量表)

2、方法区中的常量引用的对象

3、方法区中的类静态属性引用的对象

4、本地方法栈中 JNI ( Native 方法) 的引用对象

5、活跃线程的引用对象

回收算法

1、标记 - 清除(Mark and Sweep)

没有解决内存碎片化的问题

mark

2、复制算法 (Copying)

分为对象面和空闲面,对象在对象面上创建,存活的对象被从对象面复制到空闲面,将对象面所有对象内存清除,解决了内存空间碎片化的问题,合适只有少数存活对象的情况,因为只复制一小部分对象就 OK 了

mark

从图中可以看出复制算法能解决碎片化问题,顺序分配内存,简单高效。适用于对象存活率低的场景。

3、标记整理算法(Compacting)

标记:从根集合进行扫描,对存活的对象进行标记。清除:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。

mark

标记整理算法避免内存的不连续性,不用设置两块内存互换,但是效率成问题,如果第一个对象被标记为可回收,那么剩下的对象都要往前移动,损失性能。

4、分代收集算法(Generational Collector)

分代收集算法是垃圾回收算法的组合拳,按照对象生命周期的不同划分区域以采用不同的垃圾回收算法,目的在于提高 JVM 的回收效率。

在 JDK6、JDK7 中,Heap 主要分为:新生代、老年代和永久代,JDK8 以及以后的版本中去掉了永久代,而且使用 MetaSpace 替代,新生代对象存活率低,宜采用复制算法,老年代对象存活率高,采用标记清除算法或标记整理算法。

GC 的分类

Minor GC:发生在新生代中的垃圾收集动作,采用的是复制算法,新生代是垃圾收集比较频繁的区域

Full GC:一般对老年代的回收会伴随着新生代的垃圾收集

现在主要看看新生代,新生代主要分为一个 Eden 区和两个 Survivor 区(to 区和 from 区,在垃圾收集中相互转换),对象一开始被创建的时候就是放在 Eden 区的,如果 Eden 去放不下,就会放到 Survivor 区甚至是老年代中。Eden 区:两个 Survivor 区默认比例是 8:1:1

mark

对象年龄在默认情况下超过 15 岁(意思是经过 15 次 Minor GC)就会晋升到老年代,Survivor 去放不下的时候也会进入到老年代中,通过设置 -XX:+PretenuserSizeThreshold 参数来指定如果对象的大小查过指定值对象一生成就直接放入老年代,常用的性能调优的参数:

-XX:SurvivorRatio:Eden 和 Survivor 的比值,默认 8 : 1

-XX:NewRatio:老年代和新生代内存大小的比例

-XX:MaxTenuring Threshold:对象从新生代晋升到老生代经过 GC 次数的最大阈值

老年代使用的最多的是标记清除算法或者标记整理算法进行垃圾回收,通常触发老年代的垃圾回收的时候也会触发新生代的垃圾回收,这便是 Full GC。Full GC 比 Minor GC 慢,但是执行的频率很低。

触发 Full GC 的条件

1、老年代空间不足

2、永久代空间不足

3、CMS GC 时出现 promotion failed , concurrent mode failure

当使用 CMS 垃圾收集器的时候,如果出现了 promotion failed,那就说明在进行 Minor GC 时,Survivor 空间不足,对象只嗯呢该放入老年代,但是此时老年代也出现了空间不足,就会出现 promotion failed,就会触发 Full GC; concurrent mode failure 也是 CMS 垃圾收集器在执行的过程中,同时有对象要放入老年代,此时老年代空间不足,就会造成 concurrent mode failure,从而触发 Full GC

4、Minor GC 晋升到老年代的平均大小大于老年代的剩余空间,Hotspot 在设计时,在进行 Minor GC 的时候进行了判断,如果之前统计得到的 Minor GC 的平均大小已经达到了老年代的剩余空间大小,就会直接触发 Full GC。比如第一次 Minor GC 剩余的对象大小为 6M,如果此时老年代剩余空间小于 6M,就会触发 Full GC。

5、调用 System.gc (),只是提醒一下虚拟机此时应该触发 Full GC,至于回不回收由虚拟机自己决定,只是 System.gc () 增加了 Full GC 的可能。

6、使用 RMI 来进行 RPC 或管理的 JDK 应用,每小时执行 1 次 Full GC。