---
title: JVM GC 并发机制与三色标记算法
type: concept
domain: java-concurrency
tags: [jvm, gc, three-color, concurrent-gc, write-barrier, read-barrier, zgc, shenandoah]
status: learning
created: 2026-09-22
last_reviewed: 2026-09-22
method: feynman
related_questions:
  - "什么是三色标记算法？"
related_knowledge:
  - ./jvm-gc-algorithms.md
  - ./jvm-gc-select-tune.md
  - ./jvm-memory-model.md
anki_cards: 5
interview_rounds:
  - "2026-09-22-round-1-Q8"
---

# JVM GC 并发机制与三色标记算法

> 三色标记是并发 GC 的核心。三色定义**白=初始、灰=工作队列、黑=确定存活**。并发下两大问题：**对象消失（漏回收）**和**对象复活（误回收）**。修复靠**写屏障**或**读屏障**。

## TL;DR（30 秒扫完）

- 三色严格定义：**白=初始未访问** / **灰=工作队列** / **黑=扫描完毕**
- **不变式**：黑不能指白（否则漏回收）；灰不能指黑（否则误回收）
- **两大并发问题**：对象消失（漏回收，黑指白）+ 对象复活（误回收，白指黑）
- **两种修复**：**写屏障**（修复黑指白，CMS/G1/ZGC 经典）+ **读屏障**（修复白指黑，Shenandoah/ZGC 现代）
- ZGC 用**读屏障+多映射**把 STW 压到 <1ms

## 关键结论

- **结论 A**：三色标记是可达性分析的**并发实现**，本质相同
- **结论 B**：GC 目标 = 遍历结束后**没有白色对象**指向存活对象（等价：没有白色对象）
- **结论 C**：写屏障修复黑指白，读屏障修复白指黑
- **结论 D**：ZGC 从写屏障转向读屏障——避免每次赋值都触发，改用多映射实现"移动对象不改引用"
- **结论 E**：Shenandoah 用**写屏障+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 从写屏障转向读屏障**？
- 写屏障每次赋值都触发，影响吞吐
- 读屏障只在读取指针时检查，对代码侵入小

**多映射（Multi-Mapping）原理**：
- 同一虚拟地址映射到多个物理地址
- 移动对象时**改映射而非改对象引用**
- 配合读屏障检查指针有效性
- 效果：**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**

## 常见误区

- **误区 1**："初始颜色是黑色" → 正确：**白色是初始状态**，从未被 GC 访问
- **误区 2**："标白后再回收" → 正确：**白色对象本身就是垃圾**（初始未被访问），不是"标白后再回收"
- **误区 3**："灰色是不确定存活" → 正确：灰色是**扫描中的中间态**，不是"不确定"
- **误区 4**："三色标记就是标记回收" → 正确：三色标记是可达性分析的**并发实现**，解决并发下的内存安全
- **误区 5**："三色标记所有 GC 器都用" → 正确：SerialGC 不需要（不需要并发）

## 延伸追问

1. **三色标记的"对象消失"具体怎么走通？**
   - 黑 X 引用白 Y → 扫描 X 完成断链 X→Y → 用户创建黑 Z 引用 Y → 若 Y 从未被访问保持白 → 被误回收
2. **写屏障具体怎么处理？**
   - 写屏障在对象引用赋值时插入：如果新引用指向白对象，把父对象重染灰；HotSpot 中通过 Card Table 实现
3. **为什么 ZGC 从写屏障转向读屏障？**
   - 写屏障频繁触发（每次赋值）影响吞吐；读屏障只在读取指针时检查，对代码侵入小；配合多映射实现"移动对象不改引用"
4. **Shenandoah 的 Brooks 转发是什么？**
   - 对象头存转发指针，读屏障检查有效性时顺带完成对象移动；STW <10ms
5. **三色标记和可达性分析的关系？**
   - 三色标记是可达性分析的**并发实现**；本质都是从 GC Roots 遍历，区别在是否并发、如何处理引用变动

## 速查表

```
三色定义: 白=初始未访问 / 灰=工作队列 / 黑=扫描完毕
不变式: 黑不指白（防漏回收）+ 灰不指黑（防误回收）
并发问题: 对象消失（漏回收）+ 对象复活（误回收）
写屏障: 修复黑指白（CMS / G1 / ZGC 经典）
读屏障: 修复白指黑（Shenandoah / ZGC 现代）
ZGC 现代: 读屏障 + 多映射 → STW <1ms
Shenandoah: 写屏障 + Brooks 转发 → STW <10ms
```

## 关联题目（题库）

- ⚠️ 《什么是三色标记算法？》— Round 1 Q8, ⭐（三色定义反了，未提内存安全）

## 关联知识

- [JVM GC 算法与 STW](./jvm-gc-algorithms.md)
- [JVM GC 器选型与调优](./jvm-gc-select-tune.md)
- [JVM 内存模型](./jvm-memory-model.md)
- [主题地图](./_moc.md)

## Anki 候选卡片

1. **正**：三色严格定义？**反**：白=初始未访问 / 灰=工作队列 / 黑=扫描完毕
2. **正**：三色不变式两条？**反**：黑不能指白 + 灰不能指黑
3. **正**：写屏障修复什么问题？**反**：修复"黑指白"（对象消失/漏回收）
4. **正**：读屏障修复什么问题？**反**：修复"白指黑"（对象复活/误回收）
5. **正**：ZGC 现代版用什么屏障？**反**：读屏障 + 多映射，STW <1ms

---

*最后更新：2026-09-22*
