垃圾标记与收集算法

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

标记算法

1、引用计数法

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

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

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

 1public class MyObject{
 2    MyObject object;
 3    
 4    public static void main(String[] args) {
 5        MyObject myObject = new MyObject();
 6        MyObject myObject2 = new MyObject();
 7        myObject.object = myObject2;
 8        myObject2.object = myObject;
 9    }
10}

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。