前言
CMS垃圾收集分为四个步骤
- 初始标记
- 并发标记
- 重新标记
- 并发清除
第一阶段,我们知道初始标记标记的是GC-root以及GC-root的直接引用对象,这个阶段会进入STW,因为枚举GC-root需要在一个稳定的环境下进行,因为GC-root包含了Java栈上的局部变量,若是在并发环境,则会有大量新生的Gc-root产生,万一没有标记上对后续影响很大。
第二阶段,并发标记则是以gc-root为根节点,对其引用链进行深度递归标记
第三阶段,重新标记同样会进入stw,因为第二阶段可能在并发过程中部分对象引用关系发生了改变需要重新进行标记,而这个阶段的目标是尽可能是使得存活对象和垃圾能够标记的更准确,所以也需要进入stw。
第四阶段,则采用标记清除算法进行垃圾回收。
以上就是CMS垃圾收集器的基本流程,这里提出本文的重点,第三阶段是如何进行重新标记的,如何得知哪些对象的引用关系发生了变化,需要重新标记。
三色标记法
我们将对象分为三种颜色
分别是
白色 未标记对象
黑色 已标记对象 且所有引用均已标记
灰色 已标记 但至少存在一个引用未标记
因此在第二阶段 并发标记的过程中,一开始只有gc-root为黑色,从gc-root出发顺着引用链进行三色标记时,是一幅由黑色节点 向灰色节点 去吞没白色节点的过程。
但是存在这样的一个问题,未扫描的白色节点,与灰色或者白色节点的引用关系被切断,但又被已扫描的黑色节点引用,这时候因为图的遍历时由黑 向 灰 到 白,黑色代表着该节点已经以及所有直接引用都已经标记,无需再次标记,这时新引用的白色节点,就不会被标记,那之后的清除阶段可能就会被当成是垃圾而回收,这是我们不能容忍的,这个过程将不该回收的对象回收掉,我们称之为“对象消失”。
理论证明 “对象消失”当且仅当满足两个条件时才会发生
- 插入一条或多条,黑色节点到白色节点的引用关系
- 删除了全部灰色对象到该引用的直接或间接引用关系
满足两个情况就会造成对象消失。
只要破坏任何一个条件都能够解决 “对象消失” 那么就有两种方案
增量更新
增量更新是破坏了对象消失的第一个条件当黑色节点新增了指向白色节点的引用时,将这个引用记录下来,再并发标记结束的时候,以这些黑色节点(新增指向白色节点的黑节点)为根节点,进行递归引用链进行标记
原始快照SATB
另一种方案原始快照则是,破坏第二种条件,在灰色节点删除引用关系的时候,记录这个删除位置的引用关系,然后再标记完成后以这些灰色节点为根,以曾经的引用关系进行递归,换句话说就是不管这个引用关系有没有删除,都以存在这个引用关系为基准进行递归,这样递归的这条引用链,是之前的一个快照。也同样解决了对象消失的问题,但是带来了另一种问题,那就是这样标记完,可能被删除引用关系的对象,确实就是个垃圾,但却因为快照,而留存了下来,称其为“浮动垃圾”,但是浮动垃圾是可以容忍的,因为下一次垃圾回收的时候,大概率会被回收掉。
总结
回答本文提出的问题,CMS第三阶段重新标记的依据是什么,答案是采用了 三色标记法 + 增量更新,在第二阶段并发标记时采用三色标记进行可达性分析,并且保存过程中的增量引用关系,在重新标记阶段,进行再次标记。
而本文中提到的另一种解决对象消失问题的解决方案 原始快照SATB则是G1垃圾回收器采用的方案。