stw仅发生在两个原子切片:root marking(遍历栈帧、全局变量等标灰根对象)和 mark termination(清空灰色队列残余并重扫所有goroutine栈),每次持续极短,但必须发生。

Go 的三色标记不是给对象“涂颜色”,而是运行时用 mbitmap 和 gcWork 协同推演出来的可达性视图;写屏障不是可选项,是并发标记不漏对象的硬性前提。
三色状态到底存不存在于对象内存里
不存在。白色、灰色、黑色不是对象结构体里的字段,也不占对象额外内存。它们是 GC 工作过程中由 mbitmap(标记位图)记录的逻辑状态,配合 gcWork 队列中对象的进出动态推演出来的。比如一个对象刚被扫描完子引用,GC 就把它在 mbitmap 对应位从“灰”标为“黑”,但它本身的 struct 里没有 Color 字段——你手写 demo 时加的 Color 字段只是为了教学可视化,真实 runtime 不这么干。
常见误解:看到源码里有 obj.gcmarkbits 就以为每个对象存了颜色。其实那是 span 级的位图,按地址偏移查,不是 per-object 存储。
混合写屏障为什么必须 shade(old) + shade(ptr)
它防的是并发下最危险的竞态:一个黑色对象 A 把字段从指向白色对象 C 改成指向另一个白色对象 B,而 GC 已经扫过 A,B 就再没机会被标记——直接漏标、误回收。
混合写屏障在每次 *slot = ptr 前插入,仅对堆上指针写生效(结构体字段、map value、接口赋值等),伪逻辑如下:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
if gcphase == _GCmark {
if old != nil { shade(old) } // 保旧:防止 C 因无其他路径被提前回收
if ptr != nil { shade(ptr) } // 拉新:确保 B 被标灰入队,后续扫描
}
注意:shade() 不是函数调用,是编译器内联的位图操作;栈上赋值、int/string 底层数据、unsafe.Pointer 绕过类型系统,都不触发该屏障。
STW 真实发生在哪里、持续多久
STW 只精确发生在两个原子切片:
-
root marking:暂停所有 goroutine,遍历当前所有栈帧、全局变量、寄存器,把根对象标灰——通常 -
mark termination:标记主循环结束后,再停一次,重扫所有 goroutine 栈 + 清空灰色队列残余——同样
中间整个标记推进(灰色队列消费)、清除(按页异步归还 mheap.free)、甚至内存分配(mallocgc 优先复用白色页),全部与用户代码并发执行。你看到的 go tool trace 里 “GC pause” 曲线短,并不是没 STW,而是它被压到了百微秒级且只出现两次。
容易忽略的点:栈重扫之所以可行,是因为栈空间小、生命周期短;如果依赖栈上指针做长期引用(比如把局部变量地址传给 goroutine),又不做逃逸分析干预,就可能让本该早死的对象被迫堆分配,拖慢 GC 效率。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










