Go 1.25 引入了一个名为 Green Tea GC(代号"绿茶")的全新实验性垃圾回收器。这是 Go 语言运行时(Runtime)近年来在内存管理方面最大的一次架构级调整,旨在解决现代硬件上的"内存墙(Memory Wall)"问题。
"内存墙" (Memory Wall)是计算机体系结构中的一个经典术语,用来描述 处理器(CPU/GPU)的计算速度与内存系统(主要是主存 DRAM)的数据供给能力之间日益扩大的性能鸿沟。
简单来说:CPU 越来越快,但内存"喂"数据的速度跟不上,导致 CPU 经常"饿着等饭吃"。
原因:计算能力 vs 内存带宽增长不匹配
- CPU 性能 :遵循摩尔定律(虽已放缓),通过增加核心数、提升频率、SIMD 向量化等方式,每秒可执行的运算次数(FLOPS)
- 内存带宽 :DRAM 技术(如 DDR4 → DDR5)进步缓慢,带宽每年仅提升约 10--20%,远低于算力增长。
| 年份 | 典型 CPU FLOPS | 典型内存带宽 |
|---|---|---|
| 2000 | ~1 GFLOPS | ~2 GB/s |
| 2010 | ~100 GFLOPS | ~20 GB/s |
| 2025 | ~10+ TFLOPS | ~50--100 GB/s |
→ 算力增长了上万倍,内存带宽只增长几十倍。
这导致CPU 利用率低下:大量时间花在等待数据,而非计算
1. 核心背景:为什么要搞 Green Tea GC?
现有的 Go GC(基于并发的三色标记清除算法)是一个**以对象为中心(Object-centric)**的系统。
-
旧机制的问题 :在标记阶段,GC 会沿着指针图(Pointer Graph)一个接一个地遍历对象。这种"顺藤摸瓜"的方式在逻辑上很清晰,但在物理内存上却是随机访问(Random Access)。

-
后果:随着 CPU 核心数增加和运算速度变快,内存带宽和延迟成为了瓶颈。GC 线程在遍历指针时,经常因为 CPU 缓存未命中(Cache Miss)而停下来等待主内存数据。这种现象被称为"指针追踪(Pointer Chasing)"开销。

Green Tea GC 的目标就是打破这种随机性,提高 CPU 缓存利用率,从而降低 GC 对 CPU 的占用。
2. 技术原理:从"对象"到"页"
Green Tea GC 的核心变革在于将视点从"单个对象"转移到了"内存块"。
以 Span(内存页)为单位 :
Green Tea 不再执着于立即追踪单个对象的指针,而是以 Span(Go 内存管理中的 8KB 块)为单位进行批处理。

工作流程:
-
当 GC 发现一个对象是"活"的,它不会立刻去扫描这个对象内部的指针。
-
相反,它会将该对象所在的整个 Span 标记为"待扫描"。这些 Span 会被放入 Work List 中。同时,设置被扫描对象的 seen = 1。

-
GC 线程从 Work List 中取出一个 Span(图中的 active ),一次性线性扫描该 Span 中所有活跃的小对象,标记 scanned = 1。并将 seen 对象指向的其他对象所在的 Span 加入 Work List(不会重复添加)

-
按这个顺序循环

空间局部性(Spatial Locality) :
由于扫描是在连续的内存块上进行的,CPU 可以利用预取(Prefetching)机制高效地将数据加载到缓存中。这极大地减少了因随机访问导致的 CPU 停顿。
针对小对象优化 :
Green Tea 主要针对 512 字节及以下的小对象 进行优化。大对象依然沿用旧的逻辑,因为小对象数量庞大,是造成指针追踪开销的主要元凶。
3. 性能预期与优势
根据官方和社区的早期测试数据:
- 降低 CPU 开销 :对于重度依赖 GC 的程序,GC 阶段的 CPU 占用率可降低 10% ~ 40%。这意味着更多的 CPU 时间可以留给业务逻辑。
- 更好的多核扩展性:在拥有大量核心(如 64 核以上)的服务器上,Green Tea 表现出更好的扩展性,减少了锁竞争和总线压力。
- 为未来铺路 :这种内存布局对 SIMD(向量指令集,如 AVX-512) 非常友好。虽然 Go 1.25 的实验版本可能尚未完全启用向量加速,但这一架构为 Go 1.26 引入硬件级 GC 加速打下了基础。
视频中的一个例子:传统 GC → 7次内存扫描

Green Tea GC → 4次内存扫描**(每次将整页扫进缓存)**

4. 如何在 Go 1.25 中使用
由于目前处于实验阶段,默认是不开启的。你需要通过构建标签(Build Tag)或环境变量来启用。
启用方法:
在编译你的 Go 程序时,设置 GOEXPERIMENT 环境变量:
Bash
GOEXPERIMENT=greenteagc go build main.go
验证是否生效:
运行编译后的程序,配合 GODEBUG=gctrace=1,观察输出日志。虽然日志格式变化不大,但如果你对比开启前后的 CPU Profile(pprof),会发现 runtime.scanobject 等函数的 CPU 占用明显改变。