golang混合写屏障
混合写屏障(Hybrid Write Barrier)是 Go 语言从 1.8 版本开始采用的一种垃圾回收(GC)优化技术。它的核心目标是彻底消除垃圾回收过程中一次耗时的“暂停程序”(STW)操作,从而将 GC 导致的程序停顿时间从几十毫秒降低到亚毫秒级别,极大地提升了程序的响应速度。
要理解它,我们需要先了解它要解决的问题。
🤔 背景:并发 GC 的难题
Go 的垃圾回收是并发的,这意味着 GC 标记过程和你的程序(Mutator)是同时运行的。这带来了一个核心挑战:
当 GC 正在标记哪些对象是“存活”的,你的程序可能正在修改对象之间的引用关系。如果不加控制,就可能导致一个本应存活的对象被 GC 错误地当成垃圾回收掉。
为了解决这个问题,Go 使用了三色标记法和写屏障技术。
- 三色标记法:将对象分为白色(待回收)、灰色(已标记,待扫描)、黑色(已标记,已扫描)。
- 核心难题:必须保证一个已经被扫描完毕的黑色对象,不能指向一个还未被标记的白色对象。如果发生这种情况,这个白色对象就可能被漏标,从而被错误回收。
写屏障就是为了维护这个规则而插入的一段代码,它会在程序修改指针时执行,确保不会破坏三色标记的正确性。
⚖️ 混合写屏障的诞生
在 Go 1.8 之前,主要有两种写屏障,但它们都有各自的缺点:
-
插入写屏障 (Dijkstra Barrier)
- 规则:当把一个新指针写入对象时,将这个新指针指向的对象标记为灰色。
- 缺点:无法有效处理 Goroutine 栈(Stack)上的指针修改。为了保证正确性,GC 结束时必须暂停所有程序(STW),重新扫描所有 Goroutine 的栈,这个操作在 Goroutine 数量多时非常耗时。
-
删除写屏障 (Yuasa Barrier)
- 规则:当覆盖一个旧指针时,将这个旧指针原来指向的对象标记为灰色。
- 缺点:会产生“浮动垃圾”(Floating Garbage),即一些已经变成垃圾的对象,因为被标记为灰色而“存活”到下一轮 GC,导致内存回收不及时。
混合写屏障结合了这两种屏障的优点,巧妙地规避了它们的缺点。
⚙️ 混合写屏障如何工作
混合写屏障的核心思想可以概括为以下四点:
-
GC 开始时,栈全黑
在 GC 开始的短暂 STW 阶段,将所有 Goroutine 栈上的可达对象一次性全部标记为黑色。这意味着,从 GC 的视角看,栈上引用的所有对象在初始时都被认为是存活的。 -
栈上新对象,直接为黑
在 GC 期间,任何在栈上新分配的对象,都会被直接标记为黑色。 -
堆上指针修改,新旧都灰
这是混合写屏障的核心。当在堆(Heap)上修改一个指针时,会同时执行插入和删除屏障的逻辑:- 将旧指针指向的对象标记为灰色(删除屏障)。
- 将新指针指向的对象标记为灰色(插入屏障)。
这个操作可以用伪代码表示:
// *slot = new_ptr shade(*slot) // 将旧对象染灰 (删除屏障) shade(new_ptr) // 将新对象染灰 (插入屏障) *slot = new_ptr
✨ 为什么它能消除 STW 重扫栈?
正是因为“栈初始全黑”和“堆上双向保护”这两个设计,使得在并发标记阶段结束后,不再需要重新扫描栈:
- 初始状态已覆盖:GC 开始时,栈上所有存活对象都已被标记。
- 新对象已保护:GC 期间栈上新分配的对象直接是黑色,不会被漏掉。
- 堆对象已保护:任何从堆对象中删除的引用,都会通过删除屏障被标记为灰色,确保其下游对象能被 GC 追踪到。
因此,GC 标记阶段结束后,所有存活对象都已被正确标记,无需再花费时间重新扫描栈。
📌 总结:优势与代价
-
优势:
- 极短的 STW:彻底消除了标记结束时的 STW 栈重扫,将 GC 停顿时间从几十毫秒降低到亚毫秒级别。
- 高正确性:通过堆上的双向保护,确保了并发标记的正确性。
-
代价:
- 保留浮动垃圾:它继承了删除写屏障的缺点,即会产生一些“浮动垃圾”,这些垃圾会多存活一个 GC 周期,但这通常是可以接受的权衡。

浙公网安备 33010602011771号