垃圾回收(2)CMS

1、CMS介绍

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

一、并行,STW时间短暂。
二、没有压缩和整理,产生内存碎片。数据结构

对象在标记过程当中,根据标记状况,分红三类:并发

一、白色对象,表示自身未被标记;
二、灰色对象,表示自身被标记,但内部引用未被处理;
三、黑色对象,表示自身被标记,内部引用都被处理;ide

垃圾回收(2)CMS

触发时间:若是添加了一下参数oop

垃圾回收(2)CMS

当老年代的使用率达到80%时,就会触发一次cms gc。线程

2、CMS的收集过程分为6个步骤。

假设CMS GC以前的堆结构以下图:
垃圾回收(2)CMS对象

一、初始标记(InitialMarking)

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

结果:
垃圾回收(2)CMS递归

二、并发标记(Marking)

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

结果:

垃圾回收(2)CMS

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

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

可是Young GC若是发生,比方说:
一、并发标记还未扫描到脏卡1.
二、Young GC扫描完脏卡,并改变dirty到clean.
三、并发标记扫描,发现卡1已不是脏卡,则不会处理,这就形成了漏标。

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里面被标记的项。

三、预清理(Precleaning&AbortablePreclean)

3.1 Precleaning

经过参数CMSPrecleaningEnabled选择关闭该阶段,默认启用,主要作两件事情:

一、处理新生代已经发现的引用,好比在并发阶段,在Eden区中分配了一个A对象,A对象引用了一个老年代对象B(这个B以前没有被标记),在这个阶段就会标记对象B为活跃对象。
二、在并发标记阶段,若是老年代中有对象内部引用发生变化,会把所在的Card标记为Dirty(包括ModUnionTalble),经过扫描这些Table,从新标记那些在并发标记阶段引用被更新的对象。

3.二、AbortablePreclean

该阶段发生的前提是,新生代Eden区的内存使用量大于参数CMSScheduleRemarkEdenSizeThreshold 默认是2M,若是新生代的对象太少,就没有必要执行该阶段,直接执行从新标记阶段。

为何须要这个阶段,存在的价值是什么?

由于CMS GC的终极目标是下降垃圾回收时的暂停时间,因此在该阶段要尽最大的努力去处理那些在并发阶段被应用线程更新的老年代对象,这样在暂停的从新标记阶段就能够少处理一些,暂停时间也会相应的下降。

在该阶段,主要循环的作两件事:

一、处理 From 和 To 区的对象,标记可达的老年代对象
二、和上一个阶段同样,扫描处理Dirty Card和ModUnionTalble中的对象。

固然了,这个逻辑不会一直循环下去,打断这个循环的条件有三个:

一、能够设置最多循环的次数 CMSMaxAbortablePrecleanLoops,默认是0,意思没有循环次数的限制。
二、若是执行这个逻辑的时间达到了阈值CMSMaxAbortablePrecleanTime,默认是5s,会退出循环。
三、若是新生代Eden区的内存使用率达到了阈值CMSScheduleRemarkEdenPenetration,默认50%,会退出循环。

四、从新标记(STW的过程)

在以前的并行阶段,可能产生新的引用关系以下:

一、老年代的新对象被GC Roots引用
二、老年代的未标记对象被新生代对象引用
三、老年代已标记的对象增长新引用指向老年代其它对象
四、新生代对象指向老年代引用被删除

上述对象中可能有一些已经在Precleaning阶段和AbortablePreclean阶段被处理过,但总存在没来得及处理的,因此须要作如下事情:

一、遍历新生代对象,从新标记
二、根据GC Roots,从新标记
三、遍历老年代的Dirty Card和Mod Union Table,从新标记

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

CMS算法中提供了一个参数:CMSScavengeBeforeRemark,默认并无开启,若是开启该参数,在执行该阶段以前,会强制触发一次YGC,能够减小新生代对象的遍历时间,回收的也更完全一点。

五、并发清理

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

六、重置

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

相关文章
相关标签/搜索