三色标记法 (Three-Color Marking) 是 Go 实现并发 GC 的核心算法。它通过将对象分为黑、灰、白三种颜色,巧妙地实现了在程序运行的同时,准确找出不再使用的内存。

核心逻辑:三种颜色的身份牌

在标记过程中,每个对象都会被贴上一种颜色的标签:

颜色状态描述形象类比
白色尚未被发现的对象潜在垃圾:标记结束仍为白色则回收。
灰色已发现但未处理完待办事项:自己是活的,但还没看它引用的子对象。
黑色已确认存活且处理完安全对象:自己和子对象都已确认存活。

标记流程:从根出发的染色

graph LR
    Root((GC Roots)) --> A[对象A: 灰色]
    A --> B[对象B: 白色]
    A --> C[对象C: 白色]
    D[对象D: 白色]
    
    style Root fill:#f9f,stroke:#333
    style A fill:#ccc,stroke:#333
    style D stroke-dasharray: 5 5
  1. 初始状态:所有对象都是 白色
  2. 根扫描:从 GC Roots 出发,将直接引用的对象染成 灰色
  3. 并发标记
    • 从灰色队列取出一个对象,将其引用的子对象染成 灰色
    • 将该对象本身染成 黑色
  4. 循环往复:直到灰色队列清空。
  5. 最终清理:回收所有仍然是 白色 的对象。

挑战:并发下的“漏标”风险

在并发标记阶段,业务代码(Mutator)可能会修改指针,导致 GC “看走眼”:

痛点场景发生动作结果
对象丢失黑色对象指向了白色对象,且该白色对象的其他灰色引用断开了。白色对象被误判为垃圾。

对策:三色不变性

为了解决漏标,Go 引入了 写屏障 (Write Barrier) 来维护以下任一约束:

  • 强三色不变性:黑色对象绝对不允许指向白色对象。
  • 弱三色不变性:黑色对象可以指向白色对象,但该白色对象必须有其他灰色对象在引用它(或其祖先)。

一句话总结:三色标记法通过“灰色”这个中间状态,将原本需要一次性完成的扫描工作变成了可以分步、并发执行的任务。