昨天我们简单介绍了CMS收集器。CMS收集器是一款以获取最短回收停顿时间为目标的收集器。它共有四个步骤:

1)初始标记:只标记GC Roots直接关联的对象。这一步非常快,需要暂停用户线程,即Stop The World

2)并发标记:GC线程和用户线程并发执行,这一步耗时较长,但可以和用户线程一起进行。

3)重新标记:因为在并发标记阶段,GC线程和用户线程并发执行,因此会出现已经被GC线程标记的对象,又被用户线程变更。重新标记就是处理这不分对象。这一步耗时比初始标记长,但是比并发标记短,需要暂停用户线程,即Stop The World。

4)并发清除:使用的是标记清除算法,不涉及对象移动因此这一步也是可以和用户线程一起运行的。

关于CMS收集器的其他内容,请参考:CMS垃圾收集器(一):CMS入门

今天我们来看CMS收集器是怎么处理并发标记问题的。以标记清除算法为基础的垃圾回收器,“标记”是所有垃圾回收器的共同特征。标记需要从GC Roots节点一直向下探索对象图。因此堆越大,所能存储的对象就越多,标记就需要越来越多的时间。CMS的最短回收停顿时间压榨的时间,主要在并发标记阶段。

想解决或者降低用户线程的停顿,就先要搞清楚为什么必须在一个能保障一致性的快照上才能实现对象图的遍历。我们用三色标记(Tri-Color Marking)来描述并发时GC线程和用户线程对对象可达性上造成的影响。三色为:

  • 白色:对象没有被垃圾回收器访问过。在初始阶段,所有的对象都是白色的,在可达性分析后如果对象还是白色,则说明这个对象是不可达的,需要被回收。

  • 黑色:对象已经被垃圾回收器访问过,且这个对象所有的引用都已经被扫描过。黑色代表这个对象被扫描过,且是存活的,如果有其他对象引用指向黑色对象,则无需重新扫描。黑色对象不可直接指向某个白色对象。

  • 灰色:对象已经被垃圾收集器扫描过,但这个对象上还存在至少一个引用没有被扫描过。

我们来演示下只有GC线程工作和GC线程与用户线程一起工作的情况。

三色标记:初始状态

初始阶段:只有GC Roots是黑色的。箭头的方向代表引用的方向。如上面:GC Roos引用一个对象。一个对象只有被黑色对象引用才能存活。

三色标记:扫描过程

扫描过程:从GC Roots开始,沿着引用链不断扫描,灰色对象是黑色对象和白色对象的分界点。

三色标记:扫描完成

扫描完成:这时候黑色对象就是存活的对象,白色对象就是已经死亡的对象,需要被回收。

上面这个图展示了GC线程标记后用户线程又修改了引用关系。上面的情况是:灰色对象1到白色对象3的引用被切断。同时已经扫描过的黑色对象2建立了和白色对象3的关系。这种情况非常危险,因为黑色对象2不会被重新扫描,导致实际存活的对象3被认为是死亡。

在1994年Wilson在理论上证明了,当且仅当以下两个条件同时满足时,会产生“对象消失”的问题,即:应该是黑色的对象被误标记为白色。

  1. 赋值器插入了一条或多条从黑色对象到白色对象的新引用

  2. 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用

因此要解决并发标记时对象消失问题,只需要破坏这两个条件的任意一个即可。由此产生了两个方案:

  1. 增量更新(Incremental Update):破坏第一个条件。当黑色对象被插入指向白色对象的引用关系时,就将黑色对象记录下来,等并发标记结束之后,再以黑色对象为根重新扫描。也可以简单理解为:只要被插入指向白色对象的引用,黑色对象就变成了灰色对象。

  2. 原始快照(Snapshot At The Begining):破坏第二个条件。当灰色对象要删除指向白色对象的引用关系时,将这个引用关系记录下来。在并发标记结束之后,再以这些记录下来的引用关系中的灰色对象为根,重新扫描一次。可以简化理解为:在引用关系删除的那一刻,将引用关系以快照的方式存下来 ,之后再进行搜索。

CMS使用的是增量更新;G1使用的是原始快照。

并发标记还会产生“浮动垃圾”:原本死亡的对象,被标记为存活。这种情况影响不大,只是会产生一些无法回收的垃圾,在下次垃圾回收时回收即可。

因为没有合适的图片,将CMS收集器运行示意图贴在这里:

举报/反馈

myydream

84获赞 42粉丝
坚持会产生奇迹,期待
关注
0
0
收藏
分享