JVM 三色标记中:增量更新与原始快照

7

下面用最简单、最容易理解的例子增量更新原始快照讲清楚。你可以把这两个机制想象成:

  • 增量更新:处理 “新加的引用”

  • 原始快照:处理 “被删除的引用”


🟦 背景(必须知道)

三色标记法:

  • 白色:未标记,可能是垃圾

  • 灰色:被发现但子对象未扫描

  • 黑色:扫描完成

并发标记时,程序还在运行,会随时修改对象引用。这些引用修改会破坏三色不变式,所以需要 写屏障(Write Barrier) 记录这些变化,等标记结束后“补扫”(Remark)。


🟩 第一部分:增量更新(Incremental Update)

破坏的条件:黑色对象不能指向白色对象
作用:处理 “新加的引用”

🎯 重点一句话

如果一个黑色对象新增引用一个白色对象,就把这个黑色对象重新变成灰色,留待重新扫描。

🧩 简单例子

假设有对象:

A -> B -> C

开始并发标记后:

  • A 已扫 →

  • B 已扫 →

  • C 尚未扫描 →

此时应用线程做了一个动作:

B(黑).next = C(白)

这就违反了三色规则:黑不能直接指向白

所以增量更新的写屏障会记录:

记录:B 新增引用 C

并且把 B 重新标为灰色:

B: 灰色

标记结束前,会重新从 B 开始扫描,把 C 加入存活。

📝 结果

C 不会被错误地回收,因为 B 被“拉回来”扫描了一次。


🟥 第二部分:原始快照(Snapshot-at-the-Beginning,SATB)

破坏的条件:灰色对象指向白色对象的引用不能在扫描前被删除
作用:处理 “被删除的引用”

🎯 重点一句话

从并发扫描开始那一刻起,那些原来存在的引用,就算后来被删掉,也要按“快照”继续把它当作存在来处理。

🧩 简单例子

对象关系最开始为:

A -> B -> C

标记开始时,此图被当作快照。
并发扫描时:

  • A 扫完 →

  • B 正在灰色队列(灰)

  • C 还未扫描(白)

这时应用线程删除了引用:

B(灰).next = null   // 删除指向 C 的引用

按三色规则:
如果 B(灰色)删掉指向 C(白色)的引用,C 就可能被“漏标”。

所以 SATB 执行写屏障:

记录:B 删除了对白色对象 C 的引用

等扫描结束时,会补扫:

从 B(记录的对象)重新扫描,把 C 当做存活处理

📝 结果

C 仍然不会被误回收,因为它是快照开始时存活的对象。


🟧 把两者放在一起理解(最重要)

机制

关注的事情

写屏障记录的是什么

什么时候补扫

目的

增量更新

新增引用

黑色 → 白色 的“新引用”

并发标记快结束

避免白对象被漏标

原始快照

删除引用

灰色 → 白色 的“被删引用”

并发标记快结束

按快照认为白对象仍然可达


🟩 最通俗的比喻

增量更新 = “增加的人必须补登记”

你在做学生名单点名(并发标记),
某个“已经点过名的学生(黑)”忽然说:
“老师,我刚带来一个新同学(白)。”

你就必须:

  • 把这个学生重新加入点名队列(变灰)

  • 再把新来的同学补点名一次


原始快照 = “名单上有的人就算离开教室也算来过”

点名开始时名单上有 A → B → C
中途 B 说:“C 走了,我不认识他了(删除引用)。”

但老师说:

开始点名时你明明认识他,所以我还是要把他当成来过。

于是把 C 加入点名(存活)。