垃圾回收(2)CMS

一、CMS

全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器。

1、并行,STW时间短暂。
2、没有压缩和整理,产生内存碎片。

对象在标记过程中,根据标记情况,分成三类:

1、白色对象,表示自身未被标记;
2、灰色对象,表示自身被标记,但内部引用未被处理;
3、黑色对象,表示自身被标记,内部引用都被处理;

垃圾回收(2)CMS

二、CMS的收集过程分为5个步骤。

假设CMS GC之前的堆结构如下图:
垃圾回收(2)CMS

1、初始标记(InitialMarking)

这是一个STW过程,主要分两步
1、标记GC Roots可达的老年代对象;
2、遍历GC Roots下的新生代对象能够可达的老年代对象;
3、此过程不对以上可达的老年代对象进行进一步的可达扫描。

结果:
垃圾回收(2)CMS

2、并发标记和预清理(Marking&Precleaning&AbortablePreclean)

该阶段GC线程和应用线程并发执行,遍历InitialMarking阶段标记出来的存活对象,然后继续递归标记这些对象可达的对象。
这个过程应用线程在运行,可能Young GC也会发生,会发生以下的情况:
1、新生代对象晋升到老年代
2、在老年代分配对象
3、新老年代对象的引用发生变化。

结果:

垃圾回收(2)CMS

2.1、那么如何处理并发标记过程中对象的变化呢?

CMS使用上一节讲过的Card Table来解决这个问题
并发标记过程中引用发生变化的对象所在的Card,在Card Table来记录为“脏卡”,这样在后面重新标记的时候会把这些对象也当做GC Root来遍历

但是Young GC如果发生,比方说:
1、并发标记还未扫描到脏卡1.
2、Young GC扫描完脏卡,并改变dirty到clean.
3、并发标记扫描,发现卡1已不是脏卡,则不会处理,这就造成了漏标。

2.2、如果解决以上的问题呢?

CMS中,有另一种数据结构(Mod Union Table)
Mod Union Table是一个位向量,每个单元的大小只有1位,每个单元对应一个Card(Card的大小是512字节,Card Table每一个单元的大小是1个字节)
在新生代GC处理dirty card之前,先把该card在Mod Union Table里面的对应项置位。
这样,CMS在执行重新标记阶段的时候,就会扫描Mod Union Table和card table里面被标记的项。

3、重新标记(STW的过程)

1、遍历新生代对象,重新标记
2、根据GC Roots,重新标记
3、遍历老年代的Dirty Card和Mod Union Table,重新标记

在第1步骤中,需要遍历新生代的全部对象,如果新生代的使用率很高,需要遍历处理的对象也很多,这对于这个阶段的总耗时来说,是个灾难(因为可能大量的对象是暂时存活的,而且这些对象也可能引用大量的老年代对象,造成很多应该回收的老年代对象而没有被回收,遍历递归的次数也增加不少),如果在这之前发生一次YGC,这样就可以避免扫描无效的对象。

CMS算法中提供了一个参数:CMSScavengeBeforeRemark,默认并没有开启,如果开启该参数,在执行该阶段之前,会强制触发一次YGC,可以减少新生代对象的遍历时间,回收的也更彻底一点。

4、并发清理

清理在标记阶段收集标识为不可达的对象

5、重置

清除数据结构,准备下一次并发收集。

猜你喜欢

转载自blog.51cto.com/janephp/2427903