乐于分享
好东西不私藏

Go GC 为什么能做到低延迟?一次源码级深度剖析

Go GC 为什么能做到低延迟?一次源码级深度剖析

很多线上系统在压测时都遇到过这种现象:
QPS 很高
CPU 不满
但延迟突然抖动
罪魁祸首往往只有两个字:GC
但在今天的 Go 中,你会发现一个非常惊人的事实:
Go 的 GC 在百万 QPS 服务下,STW 时间可以控制在亚毫秒级。

它是怎么做到的?

这篇文章我们从 runtime 源码出发,拆解 Go GC 低延迟的核心设计。

一、先打破一个误区:Go GC 并不“快”

Go GC 的目标从来不是“吞吐量最高”。
它的设计目标是:可预测的低延迟
这是两个完全不同的优化方向。
高吞吐 GC:允许长时间 STW,但整体扫描效率高
低延迟 GC:必须把 STW 时间压缩到极低
Go 明确选择了第二条路。

二、Go GC 的核心架构:三色标记 + 并发标记

Go 采用的是:
三色标记-清除算法(Tri-color Mark-Sweep)
对象在 GC 过程中有三种颜色:
白色:未标记(可能被回收)
灰色:已发现,待扫描
黑色:已扫描
核心不变量:黑色对象不能指向白色对象
否则会发生“漏标”。
这套算法本身并不新,但 Go 的关键在于:并发执行标记阶段

三、为什么早期 GC 延迟高?

传统 GC 流程:
STW
标记全部对象
清除
恢复
问题:
标记阶段可能持续几十毫秒甚至几百毫秒
整个过程完全暂停用户线程
在高并发系统中,这是灾难。

四、Go 如何压缩 STW?

Go 的关键策略:
把大部分标记工作放到并发阶段执行
STW 只做“必须做”的事情
现在我们看 runtime 流程。
GC 周期主要在:
runtime/mgc.go
一个完整 GC 周期包含:
GC 开始(短暂 STW)
并发标记
Mark Termination(短暂 STW)
并发清除
关键点:
STW 只出现在两个非常短的阶段

五、写屏障:并发标记的核心保障

并发标记的最大问题:标记过程中,程序还在修改指针。
怎么办?
Go 引入了:写屏障(Write Barrier)
源码位置:
runtime/mbarrier.go
当一个指针写入发生时:
*ptr = newObj
实际会变成:
gcWriteBarrier(ptr, newObj)
写屏障保证:
新引用的对象不会被漏标
三色不变量不被破坏
Go 使用的是:
混合写屏障(Hybrid Write Barrier)
它结合了:
Dijkstra 插入屏障
Yuasa 删除屏障
效果:
避免全堆重新扫描
避免长时间 STW
这一步,是 Go 低延迟的关键。

六、GC 与调度器深度耦合

Go GC 不是单独线程运行的。
它与调度器(GMP 模型)强绑定。
GC worker 以 goroutine 形式运行:
每个 P 都可以参与标记
使用 work stealing 机制
负载自动均衡
也就是说:GC 本身就是并行的
源码关键函数:
gcBgMarkWorker()
这意味着:
标记阶段可以利用所有 CPU 核心
不再是单线程扫描

七、STW 为什么能做到亚毫秒?

因为 STW 阶段只做两件事:
1️⃣ Root 扫描准备
扫描栈
扫描全局变量
建立初始灰色集合
2️⃣ 标记终止校验
确认没有遗漏
关闭写屏障
而真正耗时的“对象图遍历”:全部在并发阶段完成
这就是延迟低的根本原因。

八、GC Pacer:控制频率,而不是等内存爆炸

低延迟不仅来自“怎么回收”,还来自:
什么时候回收
Go 有一个非常核心的算法:

GC Pacer

源码位置:
runtime/mgcpacer.go
核心思想:
让 GC 与分配速率保持比例
而不是:
等堆暴涨再回收
公式简化理解:
目标堆大小 = 当前存活堆 × (1 + GOGC/100)
默认 GOGC=100:
堆翻倍时触发 GC
但 Pacer 会动态调整标记速率,避免:
标记跟不上分配
突然强制 STW
这也是延迟稳定的重要原因。

九、并发清除:彻底避免长暂停

Go 甚至连 sweep 阶段都是并发的。
清除不是集中执行,而是:
在对象分配时顺带完成
分配新对象时,如果发现 span 未清理:
触发局部 sweep
这叫:Lazy Sweep
优点:
没有集中清除暂停
成本均摊到未来分配中

十、为什么 Go GC 仍然不是“最快”的?

因为 Go 做了一个工程取舍:
不做分代 GC
不做复杂逃逸优化回收策略
不做压缩式内存整理
这使它:
吞吐率不一定最高
但延迟更可预测
对服务器而言:稳定 > 极限吞吐

十一、与其他语言的对比

Java 有 G1/ZGC,延迟也很低,但复杂度极高
Python 主要依赖引用计数 + 周期检测
Rust 直接没有 GC
Go 的定位非常清晰:
自动内存管理
低延迟
简单模型

十二、总结:Go GC 低延迟的五大核心原因

并发三色标记
混合写屏障
GC worker 与调度器并行协作
Pacer 控制回收节奏
Lazy Sweep 避免集中清除
真正让 Go GC 低延迟的,不是某个技巧。
而是一整套:
面向延迟设计的系统工程。
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Go GC 为什么能做到低延迟?一次源码级深度剖析

评论 抢沙发

7 + 3 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮