漏标本质是并发标记中灰色对象断开白色对象引用且黑色对象同时建立新引用,导致白色对象脱离标记图;写屏障通过satb(赋值前记录旧引用)或增量更新(赋值后重标黑色为灰色)破坏任一条件,精准拦截跨区引用并协同记忆集保障三色标记正确性。

漏标问题本质是并发标记时,用户线程修改引用关系,导致本该被标记为存活的对象仍处于白色状态,最终被错误回收。写屏障不是代码层面的“屏障”,而是 JVM 在对象字段赋值指令附近自动插入的轻量级底层钩子,它实时捕获引用变更,从而干预三色标记过程,防止黑色对象直接指向白色对象这种违反三色约束的情况发生。
漏标发生的两个必要条件
只有同时满足以下两点,才会真正漏标:
- 一个灰色对象断开了对某个白色对象的引用(比如
objA.field = null) - 一个黑色对象在此期间建立了对该白色对象的新引用(比如
objB.field = whiteObj)
此时,whiteObj 既没被灰色对象带入扫描队列,又没被黑色对象重新拉回扫描范围,就彻底“消失”在标记图中。
写屏障如何针对性拦截
不同收集器采用不同策略破坏上述任一条件,核心都依赖写屏障触发时机:
- G1 使用 SATB(快照在开始):在字段赋值 前 拦截,把旧引用(即将被覆盖的对象)记录进 SATB 队列。这样即使 objA 断开 whiteObj,whiteObj 的“快照”还在,后续并发标记会扫描它,避免因断开而丢失
- CMS 使用增量更新:在字段赋值 后 拦截,一旦发现新引用指向白色对象(如 objB → whiteObj),立即将 objB 重新标记为灰色,确保它后续会被再次扫描,把 whiteObj 带进来
写屏障不是万能,但很关键
它不解决所有并发问题,而是精准卡在引用变更的瞬间做最小干预:
- 只对跨代/跨区引用生效(如老年代改新生代字段),同区引用通常跳过,减少开销
- 依赖线程本地缓冲(如 G1 的 SATB 队列)和批量合并,避免频繁同步影响性能
- 必须配合记忆集(Remembered Set)使用——写屏障提供“谁改了”,记忆集维护“改了谁”,共同支撑分区回收的准确性
写屏障本身不修复逻辑,它让 GC 能“看见”并发中的变化。没有它,G1 和 CMS 就无法在不长时间 STW 的前提下完成可靠标记。











