三色标记算法是可达性分析算法的一种实现方案,其目的是为了找出所有可达对象。可以减少 JVM 在 GC 过程中的 STW 时长,三色标记算法是 CMS、G1等垃圾收集器中主要使用的标记算法。
三色标记算法把对象分为三种状态:白色、灰色和黑色。
-
白色:该对象没有被标记过。
-
灰色:该对象已经被标记过了,但该对象的引用对象还没标记完。
-
黑色:该对象已经被标记过了,并且它的全部引用对象也都标记完了。
标记过程
三色标记算法的标记过程分为三个阶段:初始标记、并发标记、重新标记。
初始标记:遍历所有的根对象,将根对象和根对象直接引用的对象标记为灰色,只会扫描一层,不会往下继续扫描。这个阶段中垃圾回收器不会扫描整个堆,需要 STW。
并发标记:从灰色对象开始遍历整个对象图,将灰色对象引用的对象标记为灰色,并将上一步标记的灰色对象标记为黑色。并发标记阶段应用程序线程可能会修改对象图,因此垃圾回收器利用写屏障保证并发标记的正确性。不需要 STW,但最耗时。
重新标记:标记在并发标记阶段中被修改的对象以及未被遍历到的对象,垃圾回收器从灰色对象重新开始遍历对象图,将灰色对象引用的对象标记为灰色,并将已经遍历过的对象标记为黑色。需要 STW。
并发清除:在重新标记阶段结束后,垃圾回收器执行清除操作,将所有未被标记的对象标记为白色,对它们进行回收。
三色标记做到了和应用线程并发执行,大大降低了 GC 的停顿时长。
三色标记法可能会存在多标和漏标的问题。
可能存在的问题
三色标记算法可能会存在多标和漏标的问题。
多标
多标是指对象原本是应该被回收掉的白色对象,被错误的标记成了黑色的存活对象,从而导致这个对象没有被 GC 回收掉。(多标了黑色对象)
一般发生在并发标记阶段,该对象是有引用的,但在过程中应用程序把该对象的引用关系删除,导致该对象成为垃圾对象。
多标会产生浮动垃圾,但一般不需要解决,等待下一次 GC 就能回收掉。
漏标
和多标相反,漏标是指一个对象本来应该是黑色存活对象,但被错误标记成了白色对象,导致该对象被垃圾回收。(漏标了黑色对象)
如上图所示,一个灰色对象引用了白色对象,在还没扫描到白色对象的时候,灰色对象取消了对白色对象的引用,黑色对象引用了这个白色对象,由于黑色对象被认为是已经标记了所有的引用对象,所以不会扫描这个新引用的白色对象,导致直到最终扫描结束白色对象还是白色,最终被清除。而实际上白色对象已经被引用了,是不能清除的。
针对漏标问题,在 CMS 和 G1 的解决方案不一样:
-
CMS:增量更新方案。
-
G1:原始快照方案。
漏标问题发生必须同时满足这两个条件:
-
至少有一个黑色对象在自己被标记之后指向了这个白色对象;
-
所有灰色对象在自己引用扫描完成之前删除了对白色对象的引用。
增量更新破坏了第一个条件,原始快照破坏了第二个条件。
增量更新
增量更新就是实时记录变化,确保每一次变化都会被重新检查,是 CMS 针对漏标问题的解决方案。
如果有黑色对象在自己标记后,又重新指向了白色对象,那么就把这个黑色对象的引用记录下来,在重新标记阶段再以这个黑色对象为根,对其引用进行重新扫描。这样黑色对象引用的白色对象就会变成灰色,从而变为存活状态。
原始快照
原始快照就是基于 GC 开始时的状态做决策,忽略之后的变化,是 G1 针对漏标问题的解决方案。
如果灰色对象在扫描完成前删除了对白色对象的引用,那么就在灰色对象取消引用之前,先将灰色对象引用的白色对象记录下来。在重新标记阶段再以这些白色对象为根,对它的引用进行扫描。