在 JVM 并发垃圾收集(GC)中,三色标记算法是实现 “GC 线程与用户线程并行执行” 的关键技术,它解决了并发场景下 “如何准确标记存活对象” 的核心问题,是 CMS、G1 等现代收集器的底层基础。
一、三色标记的核心:三种颜色的定义与状态
算法通过 “颜色” 标记对象的标记阶段和存活状态,共三种颜色,对应不同角色:
颜色 | 名称 | 核心定义 |
---|---|---|
白色 | 未标记 | 初始状态,对象尚未被 GC 线程访问。标记结束后仍为白色,判定为 “死亡对象”(可回收)。 |
灰色 | 待处理 | 对象已被 GC 线程访问,但该对象的所有子引用尚未遍历(后续需继续处理子对象)。 |
黑色 | 已处理 | 对象已被 GC 线程访问,且该对象的所有子引用都已遍历完成(后续无需再处理)。 |
二、并发标记的核心问题:漏标与错标
并发标记时,用户线程会修改对象引用关系,导致两种关键问题,其中漏标是必须解决的致命错误:
1. 漏标(Missing Mark):存活对象被误判为死亡
- 触发场景:白色对象(未标记)被黑色对象(已处理,不再遍历)引用,且原引用它的灰色对象(待处理)的引用被删除。
- 危害:漏标会导致存活对象被错误回收,直接引发空指针异常,是致命问题。
2. 错标(False Mark):死亡对象被误判为存活
- 触发场景:本应死亡的白色对象,被其他白色对象新增引用,导致标记结束后被误判为存活。
- 影响:仅造成暂时内存泄漏,后续 GC 会重新判断并回收,属于可接受的误差。
三、解决漏标的两大方案
工业界通过两种成熟方案避免漏标,核心思路是 “记录关键引用变更,确保白色对象被正确标记”:
1. 增量更新(Incremental Update):记录新增引用
- 核心逻辑:当黑色对象(已处理)新增对白色对象的引用时,通过 “写屏障” 记录该引用,后续重新遍历这个白色对象。
- 通俗理解:黑色对象不能 “偷偷” 引用白色对象,所有新增引用必须 “报备”,确保白色对象被标记。
- 应用:CMS 收集器采用此方案,在 “重新标记” 阶段(短暂 STW)遍历记录的引用,修正标记。
2. 原始快照(SATB):记录删除引用
- 核心逻辑:当灰色对象(待处理)删除对白色对象的引用时,通过 “写屏障” 记录该删除的引用(视为 “快照”),后续仍会遍历这个白色对象。
- 通俗理解:灰色对象删除的引用要 “记下来”,即使引用没了,也要确保白色对象不会漏标。
- 应用:G1 收集器采用此方案,在 “最终标记” 阶段(短暂 STW)处理快照引用,修正结果。
四、总结
三色标记算法通过 “颜色划分对象状态”,解决了并发 GC 的标记准确性问题:
- 三种颜色清晰界定对象的 “未标记 - 待处理 - 已处理” 状态;
- 漏标是致命问题,需通过增量更新(CMS 用)或原始快照(G1 用)解决;
- 错标影响较小,可通过后续 GC 弥补。