深入理解 JVM 并发标记:增量更新与 SATB 到底在防什么?
深入理解 JVM 并发标记增量更新与 SATB 到底在防什么摘要在三色标记算法的并发标记过程中如何防止对象“漏标”是现代垃圾回收器如 CMS、G1必须面对的终极挑战。很多开发者容易混淆 CMS 的增量更新和 G1 的 SATB甚至认为它们是解决不同场景的独立问题。本文将带你拨开迷雾直击漏标的充要条件并彻底讲透 SATB 在面对“黑找白”时的底层逻辑。一、 并发标记的“幽灵”漏标危机在了解解决方案之前我们必须先明确问题出在哪里。三色标记法将对象分为三种状态⚪白色尚未被垃圾回收器访问的对象。灰色已被访问但其引用的对象尚未全部访问。⚫黑色已被访问且其引用的对象已全部访问。当垃圾回收线程GC与用户线程并发执行时用户线程可能会在 GC 眼皮底下修改对象的引用关系。这就可能导致一个致命问题一个本该存活的对象没有被任何灰色对象扫描到最终保持白色被当成垃圾回收了。这就是所谓的“漏标”。漏标的“两个充要条件”Wilson 在 1994 年的论文中明确指出在并发标记过程中一个白色对象被漏标当且仅当同时满足以下两个条件条件一黑找白一个黑色对象新增了对该白色对象的引用。条件二灰断白所有灰色对象都删除了对该白色对象的引用或者说不存在从灰色对象到该白色对象的引用路径。破局思路呼之欲出只要我们破坏其中任意一个条件漏标就不会发生。现代 JVM 的两大流派正是分别盯上了这两个条件。二、 CMS 的防御之道增量更新CMS 垃圾回收器选择了破坏条件一。既然不能让“黑找白”得逞那么只要黑色对象试图引用白色对象我就把你“打回原形”。实现机制CMS 引入了写后屏障。当执行类似A.field CA 为黑C 为白的赋值操作后写屏障立刻触发将黑色对象 A 重新标记为灰色并放入重新扫描队列中。这样在后续的“重新标记”阶段GC 线程就会重新扫描 A从而发现并标黑 C。三、 G1 的防御之道SATBG1 收集器选择了破坏条件二。它的核心思想是“起始快照”。实现机制G1 引入了写前屏障。假设有操作B.field nullB 为灰B 原本指向白 C在断开引用之前写屏障会把这个旧的引用关系即指向 C 的引用压入 SATB 队列。这就相当于在并发标记开始时给对象图拍了个快照。无论后续 B 怎么断开引用只要在快照里 C 是存活的本轮 GC 就会通过处理 SATB 队列保活 C。四、 终极拷问SATB 真的防得住“黑找白”吗讲到这里很多深度思考的开发者会提出一个极其尖锐的问题“如果 SATB 只在‘删除引用’时触发那如果在并发标记期间用户线程新创建了一个白色对象并直接让一个黑色对象引用了它。此时并没有灰色对象删除引用SATB 根本不触发难道不会漏标吗”这是一个直击灵魂的问题答案是你说得对SATB 本身确实管不了单纯的“黑找白”。但是G1 并没有指望单靠 SATB 拦截所有漏标。它有一套组合拳通过另外两个层面的逻辑彻底封死了漏标的可能。破局点 1新对象的免死金牌 —— TAMS 指针对于你提到的“并发期间新创建的对象”G1 引入了TAMS (Top At Mark Start)机制。当并发标记开始时G1 会在内存区域里画一条线TAMS 之前的区域存放并发开始前已存在的老对象参与三色标记。TAMS 之后的区域存放并发标记期间用户线程新创建的对象。G1 的铁律凡是在 TAMS 边界之后新分配的对象无论被谁引用一律视为存活默认为黑色本轮绝不回收所以你场景中那个“被黑色对象新引用的白色新对象”在 G1 眼里根本不是白色它一出生就自带免死金牌绝对安全。破局点 2逻辑闭环 —— “黑找白”必然伴随“灰断白”如果用户线程在并发期间把一个**老对象原本就在 TAMS 之前的白色对象**交给了黑色对象引用呢我们要回到漏标的充要条件。如果这个老对象 C 现在是白色且被黑色对象 A 新引用了满足条件一那么它要发生漏标必须同时满足条件二没有任何灰色对象能扫描到它。既然 C 现在是白色说明它还没被 GC 线程处理。如果在并发之前 A 是黑色的那 A 在变黑之前肯定扫描过自己的引用。如果 A 当初没有引用 C说明 C 是被别人引用着的。如果在并发期间A 突然要引用 C那么用户代码必然执行了类似这样的操作从某处获取 C。将 C 赋值给 A 的字段A.field C。通常情况下为了保证内存安全原来的持有者假设是灰色对象 B会执行B.field null断开对 C 的引用。就在第 3 步执行断开操作的一瞬间SATB 的写前屏障必定触发它会把 C 记录在 SATB 队列中。结论就是在正常的 Java 对象引用图演进中纯粹的“黑找白”而不伴随任何“灰断白”的操作几乎是不存在的。只要漏标条件一被触发条件二必然在路上而 SATB 专门守株待兔拦截条件二。五、 总结对比特性增量更新SATB (Snapshot At The Beginning)代表收集器CMSG1破坏漏标条件破坏条件一黑找白破坏条件二灰断白写屏障触发时机写后屏障(赋值成功后)写前屏障(断开引用前)处理新对象依赖无特殊全局指针依赖写后屏障拦截依赖TAMS 指针将新对象直接标黑重新标记阶段工作扫描被重新标灰的黑色对象扫描 SATB 队列中记录的旧引用对象潜在副作用重新扫描范围可能较大可能产生“浮动垃圾”本该死的对象被快照保活核心一句话增量更新选择直面“黑找白”通过把黑色打回灰色来解决SATB 选择迂回认为“黑找白”必然伴随“灰断白”通过在“灰断白”时拍快照来保活对象并用 TAMS 指针解决新对象的问题。两者殊途同归共同保障了并发垃圾回收的内存安全。