TL;DR(30 秒扫完)
- 三色严格定义:白=初始未访问 / 灰=工作队列 / 黑=扫描完毕
- 不变式:黑不能指白(否则漏回收);灰不能指黑(否则误回收)
- 两大并发问题:对象消失(漏回收,黑指白)+ 对象复活(误回收,白指黑)
- 两种修复:写屏障(修复黑指白,CMS/G1/ZGC 经典)+ 读屏障(修复白指黑,Shenandoah/ZGC 现代)
- ZGC 用读屏障+多映射把 STW 压到 <1ms
关键结论
结论 A三色标记是可达性分析的并发实现,本质相同
结论 BGC 目标 = 遍历结束后没有白色对象指向存活对象(等价:没有白色对象)
结论 C写屏障修复黑指白,读屏障修复白指黑
结论 DZGC 从写屏障转向读屏障——避免每次赋值都触发,改用多映射实现"移动对象不改引用"
结论 EShenandoah 用写屏障+Brooks 转发(对象头存转发指针)做到 STW <10ms
完整讲解(费曼四步)
STEP 1 · 概念
三色标记算法是并发 GC 的核心。它给每个对象染三种颜色(白/灰/黑),表示 GC 访问进度。三色不变式(黑不指白、灰不指黑)保证并发下的正确性;违反不变式时用写屏障或读屏障修复。STEP 2 · 大白话
类比:想象你在画地图。
- 白色:还没去过(可能是垃圾也可能是幸存者)
- 灰色:去过但没画完周围的路(还在扫描中)
- 黑色:去过且画完了周围所有路(确定存活)
扫描规则:从黑画到灰、从灰画到黑。结束时只剩白色的就是垃圾。
但并发时用户线程在偷改地图——你刚划掉的一条路(黑指白),用户又建了条新路(重新指白),你可能就漏标了幸存者。这就是内存安全问题。
STEP 3 · 底层
三色严格定义
| 颜色 | 定义 | 含义 |
|---|---|---|
| 白色 | 从未被 GC 访问过 | 可能是垃圾也可能是存活(初始状态) |
| 灰色 | 已访问,但子节点未全扫描 | 工作队列,待扫描 |
| 黑色 | 已访问,所有引用都扫描完毕 | 确定存活 |
算法流程
1. 所有对象初始为白色
2. GC Roots 全部染黑
3. 取灰节点扫描子节点:白→灰入队;扫完→变黑
4. 工作队列为空时结束
5. 剩余白色对象 = 垃圾,回收
不变式(正确性条件):- 规则 1:黑不能指白(否则漏回收)
- 规则 2:灰不能指黑(否则可能误回收)
两大并发内存安全问题
问题 1:对象消失(漏回收 / 黑指白)初始状态: 黑A → 白B
第1步: 扫描黑A,把白B变灰
第2步: 扫描黑A完成,A→B 断链(用户删链)
第3步: 用户创建黑C 引用白B
第4步: B 仍是白色(从未被访问)
第5步: 遍历结束,B 被误回收 ❌
违反规则 1(黑不能指白)。
问题 2:对象复活(误回收 / 白指黑)
初始状态: 黑A → 白B
第1步: 用户创建白C 引用白B
第2步: 扫描A,A→B 断链
第3步: 遍历C,把B变灰→变黑
第4步: 用户删除 A→B 引用,创建黑A 引用白D(D 被删)
B 仍存活但被错误地认为可回收 ❌
违反规则 2(灰不能指黑)。
两种修复方案
| 修复方案 | 屏障类型 | 规则 | 使用者 |
|---|---|---|---|
| 写屏障 | Write Barrier | 对象引用赋值时:新引用指向白 → 父对象重染灰 | CMS、G1、ZGC 经典模式 |
| 读屏障 | Read/Load Barrier | 读指针时:检查有效性,无效则重新标记为黑 | Shenandoah、ZGC 现代模式 |
ZGC 的现代方案
为什么 ZGC 从写屏障转向读屏障?- 写屏障每次赋值都触发,影响吞吐
- 读屏障只在读取指针时检查,对代码侵入小
- 同一虚拟地址映射到多个物理地址
- 移动对象时改映射而非改对象引用
- 配合读屏障检查指针有效性
- 效果:STW 只有 Initial Mark 和 Final Relocate 两个瞬间,都 <1ms
Shenandoah 的 Brooks 转发
- 对象头存转发指针
- 读屏障检查指针有效性时顺带完成对象移动
- STW <10ms
与 GC 器对照
| GC 器 | 三色标记 | 屏障 | 特殊机制 |
|---|---|---|---|
| CMS | 有 | 写屏障(Card Table) | 早期经典方案 |
| G1 | 有 | 写屏障(Remembered Set) | Region 结构 |
| ZGC 经典 | 有 | 写屏障 | — |
| ZGC 现代 | 有 | 读屏障 | 多映射 |
| Shenandoah | 有 | 写屏障 | Brooks 转发 |
CMS 中的三色标记应用
- Initial Mark(STW):把 GC Roots 染黑
- Concurrent Mark:三色并发扫描(灰→黑、白→灰)
- Remark(STW):靠写屏障追踪并发期间的引用变动
STEP 4 · 简化
一句话总结:三色 = 白(未访问)/ 灰(扫描中)/ 黑(扫完);不变式 = 黑不指白 + 灰不指黑;违反时用写屏障或读屏障修复。记忆口诀:
- 白色初始,灰色队列,黑色完成
- 黑不指白防漏回收,灰不指黑防误回收
- 写屏障管赋值,读屏障管读取
- ZGC 现代:读屏障+多映射 → STW <1ms
常见误区
"初始颜色是黑色"
白色是初始状态,从未被 GC 访问
"标白后再回收"
白色对象本身就是垃圾(初始未被访问),不是"标白后再回收"
"灰色是不确定存活"
灰色是扫描中的中间态,不是"不确定"
"三色标记就是标记回收"
三色标记是可达性分析的并发实现,解决并发下的内存安全
"三色标记所有 GC 器都用"
SerialGC 不需要(不需要并发)
延伸追问
三色标记的"对象消失"具体怎么走通?
黑 X 引用白 Y → 扫描 X 完成断链 X→Y → 用户创建黑 Z 引用 Y → 若 Y 从未被访问保持白 → 被误回收
写屏障具体怎么处理?
写屏障在对象引用赋值时插入:如果新引用指向白对象,把父对象重染灰;HotSpot 中通过 Card Table 实现
为什么 ZGC 从写屏障转向读屏障?
写屏障频繁触发(每次赋值)影响吞吐;读屏障只在读取指针时检查,对代码侵入小;配合多映射实现"移动对象不改引用"
Shenandoah 的 Brooks 转发是什么?
对象头存转发指针,读屏障检查有效性时顺带完成对象移动;STW <10ms
三色标记和可达性分析的关系?
三色标记是可达性分析的并发实现;本质都是从 GC Roots 遍历,区别在是否并发、如何处理引用变动
速查表
三色定义: 白=初始未访问 / 灰=工作队列 / 黑=扫描完毕
不变式: 黑不指白(防漏回收)+ 灰不指黑(防误回收)
并发问题: 对象消失(漏回收)+ 对象复活(误回收)
写屏障: 修复黑指白(CMS / G1 / ZGC 经典)
读屏障: 修复白指黑(Shenandoah / ZGC 现代)
ZGC 现代: 读屏障 + 多映射 → STW <1ms
Shenandoah: 写屏障 + Brooks 转发 → STW <10ms
Anki 候选卡片
Q: 三色严格定义?
A: 白=初始未访问 / 灰=工作队列 / 黑=扫描完毕
Q: 三色不变式两条?
A: 黑不能指白 + 灰不能指黑
Q: 写屏障修复什么问题?
A: 修复"黑指白"(对象消失/漏回收)
Q: 读屏障修复什么问题?
A: 修复"白指黑"(对象复活/误回收)
Q: ZGC 现代版用什么屏障?
A: 读屏障 + 多映射,STW <1ms
关联题目
关联知识
三色标记不变式:黑不指白(漏回收)、灰不指黑(误回收);写屏障修黑指白、读屏障修白指黑
画地图:白=未去过、灰=去过没画完、黑=画完了;结束时只剩白色的就是垃圾
✦ 记 忆 口 诀 ✦
白灰黑三色;黑不指白灰不指黑;写屏障修黑指白,读屏障修白指黑
关键可视化
三色严格定义
flowchart TB C["三色标记"] --> W["白色<br/>从未被 GC 访问<br/>可能是垃圾或存活"] C --> G["灰色<br/>已访问但子节点未全扫描<br/>工作队列待扫描"] C --> B["黑色<br/>已访问所有引用扫描完毕<br/>确定存活"] B -.遍历.-> G G -.扫描.-> W
两条不变式
flowchart LR I["三色不变式"] --> R1["规则 1:黑不能指白<br/>否则漏回收"] I --> R2["规则 2:灰不能指黑<br/>否则误回收"] R1 -.违反时.-> F1["写屏障修复<br/>CMS/G1/ZGC 经典"] R2 -.违反时.-> F2["读屏障修复<br/>Shenandoah/ZGC 现代"]
对象消失(黑指白)修复
flowchart LR S1["初始:黑A→白B"] --> S2["扫描黑A,白B→灰"] S2 --> S3["扫描黑A完成,A→B 断链"] S3 --> S4["用户创建黑C→白B"] S4 --> S5["B 仍白(从未被访问)"] S5 --> S6["遍历结束,B 被误回收 ❌"] S3 -.写屏障.-> W["A→B 断链时<br/>把 A 染灰<br/>触发重新扫描"]
ZGC 读屏障+多映射
flowchart LR Z["ZGC 现代方案"] --> RB["读屏障<br/>修复白指黑"] Z --> M["多映射<br/>移动对象不改引用"] RB --> P["避免每次赋值都触发<br/>改用读取时修复"] M --> P2["对象移动后<br/>旧地址自动转发到新地址"] P --> S["STW < 1ms"] P2 --> S
知识关系
⬆️ 前置(Prerequisite)
jvm-gc-algorithms🔄 延伸(Extends)
jvm-gc-select-tune
🎯 概念
📏 规则
⚠️ 误区
🔍 追问
✨ 口诀
共 0 张卡,点击翻面