专栏/CMS垃圾回收器

CMS垃圾回收器

2020年07月19日 16:18--浏览 · --喜欢 · --评论
粉丝:9文章:1

前言

在看CMS的时候发现网上的资料大都很零散,学起来一头雾水,所以将网上的资料整理总结了一下。

1、什么是CMS

CMS全称 Concurrent Mark Sweep,是老年代垃圾收集器,在收集过程中可以与用户线程并发操作。它可以与Serial收集器和Parallel New收集器搭配使用,使用标记-清除算法,采用并行的方法,STW(停顿)时间很短。

运行示意图

CMS运行过程

  1. 初始标记,会导致stw。

  2. 并发标记,与用户线程同时运行。

  3. 预清理,与用户线程同时运行。

  4. 可被终止的预清理,与用户线程同时运行。

  5. 重新标记,会导致stw。

  6. 并发清除,与用户线程同时运行。

  7. 并发重置,与用户线程同时运行。

垃圾回收算法基本就那么几种,无论使用哪种算法,标记都是必要的一步。首先需要标记出垃圾,再进行回收,而标记也可以有很多种方法,CMS使用的是三色标记法。

2、三色标记法 

最终结果:A/D/E/F/G 可达

我们把遍历对象图过程中遇到的对象,按“是否访问过”这个条件标记成以下三种颜色:

  • 白色:尚未访问过。

  • 黑色:本对象已访问过,而且本对象 引用到 的其他对象 也全部访问过了。

  • 灰色:本对象已访问过,但是本对象 引用到 的其他对象 尚未全部访问完。全部访问后,会转换为黑色。

三色标记法的标记过程

最后被标记为白色的对象即为GC Roots 不可达,可以进行回收。

如果这个标记过程是STW的话,对象的引用关系是不会变的,意味着标记结果是正确的。

但是CMS是并发的进行标记的,对象间的引用可能发生变化多标漏标的情况就有可能发生。

2.1、多标(浮动垃圾)

假设已经遍历到E(变为灰色了),此时应用执行了 objD.fieldE = null

D 到 E 的引用断开


此刻之后,对象E/F/G是“应该”被回收的。然而因为E已经变为灰色了,其仍会被当作存活对象继续遍历下去。最终的结果是:这部分对象仍会被标记为存活,即本轮GC不会回收这部分内存

这部分本应该回收 但是 没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。

另外,针对并发标记开始后的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能会变为垃圾,这也算是浮动垃圾的一部分。

2.2、漏标(读写屏障)

假设GC线程已经遍历到E(变为灰色了),此时应用线程先执行了:

E 到 G 断开,D引用 G

此时切回GC线程继续跑,因为E已经没有对G的引用了,所以不会将G放到灰色集合;尽管因为D重新引用了G,但因为D已经是黑色了,不会再重新做遍历处理。
最终导致的结果是:G会一直停留在白色集合中,最后被当作垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的。

不难分析,漏标只有同时满足以下两个条件时才会发生:
条件一:灰色对象 断开了 白色对象的引用;即灰色对象 原来成员变量的引用 发生了变化。
条件二:黑色对象 重新引用了 该白色对象;即黑色对象 成员变量增加了 新的引用。

从代码的角度看
  1. 读取 对象E的成员变量fieldG的引用值,即对象G;

  2. 对象E 往其成员变量fieldG,写入 null值。

  3. 对象D 往其成员变量fieldG,写入 对象G ;

我们只要在上面这三步中的任意一步中做一些“手脚”,将对象G记录起来,然后作为灰色对象再进行遍历即可。比如放到一个特定的集合,等初始的GC Roots遍历完(并发标记),该集合的对象 遍历即可(重新标记)。

写屏障用于拦截第二和第三步;而读屏障则是拦截第一步。
它们的拦截的目的很简单:就是在读写前后,将对象G给记录下来。

2.3、写屏障(tore Barrier)

所谓的写屏障就是在写操作前后加入一些处理,类似于AOP。

1、写屏障+SATB

当对象E的成员变量的引用发生变化时(objE.fieldG = null;),我们可以利用写屏障,将E原来成员变量的引用对象G记录下来,记录下来的就叫原始快照(Snapshot At The Beginning,SATB),后续的标记也照着SATB走。

SATB破坏了条件一:【灰色对象 断开了 白色对象的引用】,从而保证了不会漏标。

2、写屏障+增量更新

当对象D的成员变量的引用发生变化时(objD.fieldG = G;),我们可以利用写屏障,将D新的成员变量引用对象G记录下来。针对新增的引用,将其记录下来等待遍历,即增量更新(Incremental Update)。

增量更新破坏了条件二:【黑色对象 重新引用了 该白色对象】,从而保证了不会漏标。

2.4、读屏障(Load Barrier)

读屏障是直接针对第一步:var G = objE.fieldG;,当读取成员变量时,一律记录下来。

对于HotSpot:

  • CMS:写屏障 + 增量更新

  • G1:写屏障 + SATB

  • ZGC:读屏障

3、CMS的具体做法

3.1、初始标记:

    标记GC Roots可达的老年代对象;遍历GC Roots下的新生代对象能够可达的老年代对象,也就是跨代引用



3.2、并发标记:

    该阶段GC线程和应用线程并发执行,遍历InitialMarking阶段标记出来的存活对象,然后继续递归标记这些对象可达的对象。




这个过程应用线程在运行,可能Young GC也会发生,会发生以下几种情况:

  1. 新生代对象晋升到老年代

  2. 在老年代分配对象

  3. 新老年代对象的引用发生变化

对象的变化会导致上文说的漏标。CMS使用卡表(Cart Table)来解决标记过程中对象的变化。那么卡表是什么,就是将内存分为一块一块的页,卡表作为一个比特位的集合,每一个比特位可以用来表示老年代的某一区域中的对象持有的引用是否有变动。


回到上文的漏标:

当D和E的引用改变后,将对应的卡表中的位置1。

卡表还有一个作用就是发生YGC的时候用来查看有没有老年代的对象引用新生代,这样就不用每次都遍历老年代的对象的。

card table只有一份,既要用来支持young GC又要用来支持CMS。每次young GC过程中都涉及重置和重新扫描card table,这样是满足了young GC的需求,但却破坏了CMS的需求——CMS需要的信息可能被young GC给重置掉了。

为了避免丢失信息,就在card table之外另外加了一个bitmap叫做mod-union table。在CMS concurrent marking正在运行的过程中,每当发生一次young GC,当young
GC要重置card table里的某个记录时,就会更新mod-union table对应的bit。

这样,最后到CMS remark的时候,当时的card table外加mod-union table就足以记录在并发标记过程中old gen发生的所有引用变化了。

实际上HotSpot VM一般用的post-write barrier非常简单,就是无条件的记录下发生过引用关系变化的card,这里不关心对象所在的分代,所以其实只要有引用改变,其对应的card都会被记录。也就是说这个card table记录的不只是old -> young引用,而是所有发生了变化的引用的出发端,无论在old还是young。

3.3、预清理

    前一个阶段已经说明,不能标记出老年代全部的存活对象,是因为标记的同时应用程序会改变一些对象引用,这个阶段就是用来处理前一个阶段因为引用关系改变导致没有标记到的存活对象的,它会扫描所有标记为Dirty的Card。

3.4、可被终止的预定清理

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

为什么需要这个阶段?

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

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

1、处理 From 和 To 区的对象,标记可达的老年代对象

2、和上一个阶段一样,扫描处理Dirty Card和ModUnionTalble中的对象。

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

1、可以设置最多循环的次数 CMSMaxAbortablePrecleanLoops,默认是0,意思没有循环次数的限制。

2、如果执行这个逻辑的时间达到了阈值CMSMaxAbortablePrecleanTime,默认是5s,会退出循环。

3、如果新生代Eden区的内存使用率达到了阈值CMSScheduleRemarkEdenPenetration,默认50%,会退出循环。


3.5、重新标记

  1. 遍历新生代对象,重新标记

  2. 根据GC Roots,重新标记

  3. 遍历老年代的Dirty Card和Mod Union Table,重新标记

3.6、并发清理

3.7、重置


以后有时间的话再整理一份G1收集器的文章。

参考链接:

https://www.jianshu.com/p/12544c0ad5c1

https://hllvm-group.iteye.com/group/topic/44529

https://www.qedev.com/dev/78901.html


投诉或建议