并发标记正确性核心是防止漏标,即存活对象未被标记而遭错误回收;三色标记法通过白(未访问)、灰(自身访问完但子对象未全处理)、黑(自身及直接引用均完成)状态约束,要求黑对象不能直接引用白对象;漏标发生在并发时用户线程新增黑→白引用或删除灰→白路径;写屏障是主流解决方案,增量更新将新黑→白引用的源重标灰,satb在引用覆盖前记录将丢失的白对象;cms用增量更新,g1/zgc默认satb;最终需stw重新标记阶段扫描卡表/记忆集以兜底修正偏差。

并发标记的正确性保障,核心在于防止“漏标”——即本该存活的对象没被标记,最终被错误回收。这直接威胁程序安全,而错标(把垃圾标成存活)只影响效率,产生浮动垃圾。
三色标记法是逻辑基础
它用颜色抽象对象状态:
- 白色:尚未访问,初始全白;全程未被触及的对象将被回收
- 灰色:自身已访问,但引用的子对象尚未全部处理(在标记栈/队列中)
- 黑色:自身及所有直接引用的对象都已访问完成
正确性关键约束是:黑色对象不能直接引用白色对象。一旦出现,说明有路径未被扫描到,就是漏标。
漏标的两个典型场景
并发环境下,用户线程(Mutator)修改引用会打破三色约束:
- Mutator从一个黑色对象新增指向白色对象的引用(比如 obj2.field = obj4),而 obj2 已标记完成,不会被重新扫描 → obj4 漏标
- Mutator删除了所有从灰色对象到白色对象的路径(比如 obj3.field = null),而 obj3 正在扫描中,obj4 尚未被触及 → obj4 漏标
写屏障是主流解决方案
读屏障开销大、使用少;写屏障在每次引用写入时介入,成本可控,成为实际选择。两种主流策略对应上述两类漏标:
- 增量更新(Incremental Update):捕获“黑→白”新引用。写屏障发现 black → white 写操作时,把 source(黑色对象)重新标记为灰色,加入待扫描队列
- 原始快照(SATB, Snapshot-At-The-Beginning):捕获“灰→白”路径消失。写屏障在引用被覆盖前,记录下即将丢失的 white 引用(如 obj3 原来的 obj4),保证这些对象至少在本次 GC 中不被回收
CMS 使用增量更新,G1 和 ZGC 默认采用 SATB。
重新标记阶段兜底
尽管写屏障大幅降低漏标概率,但无法 100% 消除。因此并发标记后必须安排一次短暂停顿(STW)的重新标记阶段:
- 扫描在并发期间被修改过的对象卡表(Card Table)或记忆集(Remembered Set)
- 修正因 Mutator 并发动作导致的标记偏差
- 确保所有可达对象最终都被黑色覆盖











