V8老生代GC采用三色标记法实现增量并发标记,通过写屏障保障黑→白引用不漏标:白色为未访问/不可达对象,灰色为已发现待扫描对象,黑色为已完全扫描对象。

V8 引擎在老生代垃圾回收中使用三色标记法(Tri-color Marking)来实现增量式、并发式的标记过程,避免长时间的 STW(Stop-The-World),提升应用响应性。
三色标记法的基本颜色含义
三色标记将对象图中的节点分为三种状态,用颜色标识:
- 白色:尚未被访问的对象,初始时所有对象都是白色;若标记结束仍为白色,则表示不可达,可被回收。
- 灰色:已被发现但其子对象尚未全部扫描完成的对象;它们在标记队列中等待遍历。
- 黑色:已完全扫描完毕,且所有可达子对象都已标记为灰色或黑色的对象;黑色对象保证不会被误回收。
V8 如何在老生代中应用三色标记
V8 的老生代使用基于标记-清除(Mark-Sweep)的垃圾回收器(如 Orinoco),三色标记是其标记阶段的核心机制。它并非一次性完成,而是拆分成多个小任务,在 JavaScript 执行间隙(如空闲时间)逐步推进:
- 初始时,根对象(全局变量、栈上引用等)被置为灰色,其余对象为白色。
- GC 工作线程从灰色集合中取出一个对象,将其标记为黑色,并将其直接引用的白色子对象标记为灰色,加入队列。
- 由于 JS 线程可能同时修改对象引用(如赋值、删除属性),V8 使用写屏障(Write Barrier)拦截这些操作:当出现“黑色对象指向白色对象”的写入时,强制将该白色对象重新标记为灰色(即“重标记”),防止漏标。
- 当灰色集合为空,标记阶段结束;所有仍为白色的对象即为不可达,进入清除阶段。
为什么需要写屏障配合三色标记
三色标记本身要求满足“黑色对象不能指向白色对象”的不变量(Black-White Invariant)。JS 执行与 GC 并发进行时,这个约束容易被破坏。例如:
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
假设 A 是黑色、B 是白色,此时执行 A.child = B,就产生了黑→白引用。若不干预,B 将永远无法被标记,导致内存泄漏。
V8 在赋值操作前插入写屏障逻辑——一旦检测到黑→白赋值,立即将 B 重新推入灰色队列。这保证了标记完整性,代价是少量性能开销,但远小于全暂停标记。
实际效果与限制
三色标记使 V8 能把一次完整的标记过程分散到多个微任务或帧之间,显著缩短单次 GC 暂停时间(常控制在几毫秒内)。但它不改变回收本质:仍需完整遍历所有可达对象,只是节奏更平滑。
注意:三色标记本身不解决内存碎片问题,因此 V8 后续还配合整理(Compaction)或分代整理策略;它也不替代引用计数,而是与 V8 的分代设计(新生代用 Scavenge,老生代用 Mark-Sweep/Mark-Compact)协同工作。

















