go三色标记法是并发安全的可达性分析协议,本质是用白/灰/黑表征可达性确定程度,状态由gcwork与mbitmap协同推演;混合写屏障(插入+栈重扫)保障强三色不变式,stw仅限根标记和标记终止两处。

Go 的三色标记法不是“给对象上色”,混合写屏障也不是可选优化——它们是并发 GC 正确性的硬性前提。没有混合写屏障,三色标记在并发下必然漏标;没有三色状态的严格推进逻辑,GC 就无法在不停顿整个程序的前提下安全回收内存。
三色标记法的本质是可达性推演,不是颜色存储
白色不表示“垃圾”,只是“尚未确认是否可达”;灰色不是“正在处理”,而是“已确认可达、但子引用未扫完”;黑色也不是“永远安全”,只是“本轮 GC 中,它及其所有引用都已纳入闭包”。这些状态不存于对象内存中,而是由 gcWork 队列和堆位图(mbitmap)协同推演出来的运行时视图。
- 所有新分配对象默认为白色,哪怕刚被栈变量引用
- 根对象(goroutine 栈帧、全局变量、寄存器)初始全进灰色队列,这是唯一强制同步入口
- 黑色对象一旦形成,其字段指针在本轮 GC 中不再被重新扫描——所以必须靠写屏障堵住“黑→白”新路径
- 你不会在代码里看到
obj.color = black这种操作;所谓“标记”,本质是原子地将对象地址入队或翻转位图比特
混合写屏障只在堆上指针写入时触发,且不可禁用
writebarrierptr 是编译器自动插入的汇编桩,不是你能调用的函数。它只对以下场景生效:
- 结构体字段赋值:
obj.field = &otherObj - 切片元素赋值:
slice[i] = &otherObj - map 赋值(value 是指针类型):
m[key] = &otherObj - 接口赋值(底层指向堆对象):
var i interface{} = &otherObj
它不拦截栈上变量之间的赋值、string 底层数组、int 等非指针类型,也不处理 unsafe.Pointer 绕过类型系统的操作——后者完全逃逸写屏障保护。
混合写屏障的两步动作:shade(old) + shade(ptr)
当执行 *slot = ptr 且当前处于 _GCmark 阶段时,运行时会:
- 若旧值
old != nil且指向堆对象,则调用shade(old)(删除屏障语义),防止它被提前回收 - 若新值
ptr != nil且指向堆对象,则调用shade(ptr)(插入屏障语义),确保它不会漏标 - 这两步都只在标记阶段生效;STW 根扫描和清扫阶段不触发
- 栈上指针修改不走此路径——所以 GC 开始时需短暂 STW 将所有 Goroutine 栈标记为黑
STW 只发生在两个精确时刻,不是“GC=卡顿”
很多人误以为三色标记仍需长 STW,其实 Go 的 STW 仅锁定两个原子切片:
- root marking:暂停所有 goroutine,遍历当前所有栈帧和全局变量,将根对象标灰——通常
- mark termination:标记主循环结束后,再停一次,重扫所有 goroutine 栈并清空灰色队列残余——同样微秒级
中间整个标记推进、清除(按页异步回收)全部与用户代码并发执行。真正影响吞吐的是标记阶段的 CPU 占用、辅助 GC(mutator assist)导致的 goroutine 延迟,而不是 STW 时间本身。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











