Copied
Please follow the site license
报告
章节_文章 // 现场报告

文章编号: RL-GO中的垃圾回收

2026.06.03

Go 中的垃圾回收

Chavy
Chavy
#Go#GC#Memory Management
分析

摘要:从三色标记法、并发回收与写屏障机制,到 Go 1.26 默认启用的 Green Tea GC——以页为单位的批量扫描如何取代传统对象图遍历,提升缓存局部性与标记效率。

Go GC 的基本目的#

垃圾回收器负责识别堆中已经无法被程序访问的对象,并将其占用的内存重新交给内存分配器使用。栈上对象通常随函数栈帧自动释放,GC 主要管理逃逸到堆上的对象。

Go GC 起点#

  • Go 1.5:引入精确、并行、并发的标记—清扫垃圾回收器,大部分标记工作可以和用户程序并发执行。
  • Go 1.8:引入混合写屏障,将 Yuasa 删除写屏障和 Dijkstra 插入写屏障结合起来,主要解决并发标记时的栈重扫描问题,进一步缩短 Stop-The-World 时间。
  • Go 1.26:默认启用新的 Green Tea GC, 思想核心:“Work with pages, not objects”。

三色标记法#

Golang GC 用到的三色标记法属于标记清扫-算法的一种实现,核心点有:

  • 将对象分为白色、灰色和黑色三种状态
  • 白色表示对象尚未被扫描,可能是垃圾对象
  • 灰色表示对象已经确认存活,但其引用的对象尚未扫描完成
  • 黑色表示对象已经确认存活,并且其引用的对象已经扫描完成
  • 初始时所有对象为白色,将根集合直接引用的对象标记为灰色
  • 不断扫描灰色对象,将其引用的白色对象标记为灰色,扫描完成后将当前对象标记为黑色
  • 当不存在灰色对象时,剩余的白色对象就是不可达的垃圾对象

可以将标记工作拆分执行,适合实现增量和并发垃圾回收,能够减少一次性标记造成的长时间停顿。

并发垃圾回收#

Go 1.5 版本之前,GC 时需要停止全局的用户协程,专注完成 GC 工作后回恢复用户协程。之后引入并发垃圾回收机制,允许用户协程和 GC 协程并发运行。

并发标记期间应用程序可能修改对象引用,导致存活对象被错误回收,需要通过写屏障等机制维护“黑色对象不能直接指向白色对象”的不变量。

屏障机制#

3.1 强弱三色不变式#

漏标问题的本质就是,一个已经扫描完成的黑对象指向了一个被灰 / 白对象删除引用的白色对象. 构成这一场景的要素拆分如下:

(1)黑色对象指向了白色对象

(2)灰、白对象删除了白色对象

(3)(1)、(2)步中谈及的白色对象是同一个对象

(4)(1)发生在(2)之前

一套用于解决漏标问题的方法论称之为强弱三色不变式:

  • 强三色不变式:白色对象不能被黑色对象直接引用(直接破坏(1))
  • 弱三色不变式:白色对象可以被黑色对象引用,但要从某个灰对象出发仍然可达该白对象(间接破坏了(1)、(2)的联动)

插入写屏障#

插入写屏障(Dijkstra)的目标是实现强三色不变式,保证当一个黑色对象指向一个白色对象前,会先触发屏障将白色对象置为灰色,再建立引用.

删除写屏障#

删除写屏障(Yuasa barrier)的目标是实现弱三色不变式,保证当一个白色对象即将被上游删除引用前,会触发屏障将其置灰,之后再删除上游指向其的引用.

混合写屏障#

  • GC 开始前,以栈为单位分批扫描,将栈中所有对象置黑
  • GC 期间,栈上新创建对象直接置黑
  • 堆对象正常启用插入写屏障
  • 堆对象正常启用删除写屏障

Green Tea#

Go 1.25 首次引入 Green Tea 垃圾回收器。该版本中,Green Tea 仍属于实验性功能,默认不会启用,需要在编译时显式设置:

PRTCL // PLAINTEXT
GOEXPERIMENT=greenteagc go build

才能使用新的垃圾回收实现。

从 Go 1.26 开始,Green Tea 被正式设置为 Go 的默认垃圾回收器。使用 Go 1.26 编译程序时,无需设置任何实验选项,即可自动采用 Green Tea GC。

核心思想:Work with pages, not objects

传统 GC:以对象为工作单位#

marksweep-025

传统 Go 垃圾回收器采用对象图遍历方式进行标记。GC 从根集合出发,将发现的对象加入对象工作列表,随后逐个取出对象,扫描其中的指针字段,并继续标记其引用的其他对象。

由于存在引用关系的对象不一定连续分布,它们可能位于不同的内存页,甚至位于同一内存页的不同区域。因此,GC 在沿指针遍历对象图时,往往需要频繁地在不同内存位置之间跳转,导致内存访问较为分散,CPU 缓存局部性较差。每个内存页都维护相应的元数据,其中的标记位用于记录页内各对象是否已经被发现。借助标记位,同一个对象在一次标记阶段中最多只会被加入对象工作列表一次,不会被重复标记。传统对象工作列表采用后进先出方式,整体表现为类似栈的深度优先遍历。

可以将传统过程概括为:

PRTCL // TEXT
发现对象
设置对象标记位
将对象加入工作栈
取出并扫描该对象
沿对象中的指针访问其他对象

这种方式的问题不在于对象会被重复标记,而在于扫描过程中需要沿着对象引用关系频繁跳转。引用关系越复杂、对象分布越分散,GC 越难形成连续的内存访问。


Green Tea:以页为工作单位#

Green Tea 并没有放弃对象级别的精确标记,它改变的是标记工作的组织和调度方式:工作列表中不再主要保存单个对象,而是保存对象所在的内存页。

严格来说,Go 官方博客为了便于解释使用了“页(page)”这一概念,而运行时实现中主要以包含一组同尺寸对象的 span 作为批量扫描和排队单位。因此,可以将其理解为:

对象仍然是存活性判断的基本单位,但页或 span 成为了标记扫描的批处理单位。

Green Tea 为页内每个对象维护两组状态位:

  • seen 位:表示 GC 已经发现了指向该对象的指针,即该对象已经确认可达。
  • scanned 位:表示该对象本身已经扫描完成,其内部包含的指针也已经被处理。

当 GC 首次发现某个对象时,会设置该对象的 seen 位,并将它所在的页或 span 加入工作队列,而不是直接将对象本身加入工作列表。

greentea-060


Green Tea 的扫描过程#

当 GC 从工作队列中取出一个页时,会比较该页的 seen 位图和 scanned 位图:

PRTCL // TEXT
待扫描对象 = seen - scanned

也就是查找满足以下条件的对象:

PRTCL // TEXT
seen = 1
scanned = 0

这些对象已经被发现,但其内部指针尚未扫描。GC 会按照内存中的排列顺序,连续扫描该页中的多个待处理对象,并将相应的 scanned 位设置为 1。

marksweep-025

在扫描这些对象的过程中:

  1. 如果发现它们指向另一个对象,则设置目标对象的 seen 位。
  2. 如果目标对象所在的页尚未进入工作队列,则将该页加入队列。
  3. 如果目标页已经位于工作队列中,则不必重复入队,只需要更新目标对象的 seen 位。
  4. 如果某个页此前已经处理完毕,但之后又发现了该页中的其他对象,该页可以再次加入工作队列。
  5. 即使一个页能够多次入队,已经设置 scanned 位的对象也不会被重复扫描。

整个过程可以概括为:

PRTCL // TEXT
发现对象
设置对象的 seen 位
将对象所在页加入工作队列
从队列中取出一页
计算 seen - scanned
连续扫描该页中的所有待扫描对象
更新 scanned 位
将新发现对象所在的页加入工作队列

当工作队列为空,并且所有可达对象都满足:

PRTCL // TEXT
seen = scanned = 1

说明所有可达对象都已完成扫描,标记阶段结束。没有设置 seen 位的对象属于不可达对象,可以在后续清扫阶段被回收。


为什么采用先进先出的页队列#

传统对象图遍历使用的是类似栈的后进先出策略,而 Green Tea 的页或 span 工作列表采用先进先出的队列策略。这样可以让某个页在队列中等待一段时间,使更多待扫描对象逐渐积累到该页中。当该页最终出队时,GC 就能一次连续处理多个对象,而不是刚发现一个对象便立即跳转过去扫描。


优化效果#

Green Tea 将原本分散的对象扫描,转变为页内对象的批量连续扫描,从而:

  • 提高内存访问的空间局部性;
  • 提升 CPU 缓存命中率;
  • 减少跨页、跨内存区域的频繁跳转;
  • 分摊读取和处理对象元数据的成本;
  • 缩小工作队列,降低多核并发访问工作队列时的竞争;
  • 为 SIMD 向量化扫描提供更规整的数据布局。

可以用一句话总结两者的区别:

传统 GC 是“发现一个对象,就尽快扫描一个对象”;Green Tea 则是“先按页积累待扫描对象,再一次性批量扫描同一页中的多个对象”。

参考链接#

https://go.dev/blog/greenteagc

https://zhuanlan.zhihu.com/p/605315127

发布于 2026.06.03
// 文章结束
Classified
章节_06
协议编号: CC-BY-NC-SA-4.0

Go 中的垃圾回收

作者:Chavy发布日期:2026.06.03

基于 CC BY-NC-SA 4.0

05 //🤖 Bot:"..."
© 2026 Chavy
Powered by Astro