三色标记法与一些零散的思考

三色抽象

三色抽象与BFS

GC的扫描过程是对可达对象的遍历,可以看作一种多源广度优先遍历。在广度优先遍历中,会使用队列存储遍历的进度,对应于三色抽象中的灰色节点。灰色节点和黑色节点的区别在于灰色节点是当前的扫描边界,它连接着可达的白色节点,灰色节点蕴含了扫描的进度信息。

上图是对三色抽象中不同节点的描述:

  • 虚拟根节点:本不存在的节点,逻辑上为所有的根节点添加一个共同的上级节点以便描述;
  • 根节点:标记起始节点,GC中一般是栈区对象;
  • 已标记节点:从根节点可达且被标记为可达的节点;
  • 标记进度:已标记节点的边界;
  • 不可达节点:从根节点不可达的节点。

对于GC来说,正确最终标记结果应该是:可达节点不会被标记为不可达,不可达节点应尽量少地被标记为可达。对应于上图来说,就是最终除不可达区域的节点存在白色外,其它节点需全部标记为黑色,并且要使得不可达区域的节点尽可能多

三色不变性

标记过程是在一个有向图(A指向B表示A引用了B)中进行,对于增量/并发GC而言,这个有向图在遍历的同时会发生改变(节点、边的新增或删除)。在增量/并发的环境中,只使用三色抽象是无法保证标记结果的正确性的。
若有向图经过一些变化,当一个白色节点被且仅被至少一个黑色节点(包括图中的虚拟根节点)和任意多个不可达白色节点所指向时,这一白色节点本身可达,但最终会被标记为不可达。这是因为这个白色节点被放在了标记进度过去的位置,而进度只能往未来继续,无法时光倒流向前回溯。
为了增量/并发(不能直接通过阻止节点或边的增减来避免这种错误),因此需要一些方法来防止这样的错误发生。在描述具体的方法之前,也许描述这些方法所具有的能力或特性更为重要。

以下是两种可以阻止错误发生的两种能力:

  1. 避免黑色节点指向白色节点。
  2. 黑色节点可以指向白色节点,但必须使得该白色节点对于任意一个灰色节点是可达的。

这两种能力被称为三色不变性,前者是强三色不变性,后者是弱三色不变性。有向图的动态变化实际上造成的是标记边界的不确定性,而三色不变性维系的是标记边界的确定性。

完美划分?

三色不变性实际保证的是可达节点不会被标记为不可达,但是不对不可达节点应尽量少地被标记为可达做出保证。那么有没有能够在有向图会动态变化的情况下,保证在某一时刻实现完美划分(所有可达节点是黑色,不可达节点是白色)的办法呢?

屏障技术

维护三色不变性

插入写屏障

删除写屏障

混合写屏障