什么是 GC
GC(Garbage Collection,垃圾回收)是一种自动内存管理机制,用于自动识别并回收程序中不再使用的内存(即“垃圾”)。程序员无需手动 free 或 delete,GC 会在运行时找到这些不可达的对象并释放其占用的内存,避免内存泄漏和悬空指针。
Go 的 GC 机制
Go 的 GC 经历了多次演进,从最初的简单标记清除,到现在的 并发、三色标记、混合写屏障 的算法。当前版本(Go 1.20+)的核心特点如下:
1. 三色标记法(Tri-color Marking)
将内存中的对象分为三种颜色:
- 白色:尚未被扫描的对象,可能是垃圾。回收结束后,所有白色对象都会被清除。
- 灰色:已被标记但尚未扫描其引用的对象(即已经访问到,但它的子对象还没处理)。
- 黑色:已被扫描且其引用的所有子对象都已被标记(即该对象是存活且已完全处理)。
过程:
- 初始:所有对象都是白色。
- 根对象扫描:从根对象(栈、全局变量、寄存器等)出发,将其引用的对象标记为灰色,放入工作队列。
- 并发标记:不断从工作队列取出灰色对象,扫描其引用,将引用的白色对象标记为灰色,并将当前对象变为黑色。
- 重复直到没有灰色对象。
- 清除:所有白色对象即为不可达的垃圾,回收内存。
2. 并发执行(Concurrent GC)
Go 的 GC 是并发的,即大部分标记工作与用户程序(mutator)同时运行,避免了长时间的 “Stop-The-World”(STW)停顿。
- 标记准备阶段(短暂 STW):开启写屏障,启动后台标记 worker。
- 并发标记阶段:标记工作与用户代码并发执行,通过后台 goroutine 和调度器协作。
- 标记终止阶段(短暂 STW):关闭写屏障,完成最终标记。
- 并发清除阶段:释放白色对象的内存,可以边分配边清除,不需要 STW。
3. 写屏障(Write Barrier)
为了保证并发标记的正确性(避免漏标),Go 使用 混合写屏障(hybrid write barrier,结合了 Dijkstra 插入屏障和 Yuasa 删除屏障)。其核心作用是:当用户程序修改指针时,在写操作前或后插入一个屏障,确保新的指针关系被正确处理,不会出现“白色对象被黑色对象引用”的漏标情况。
Go 的混合写屏障简化了屏障逻辑,仅在标记期间启用,且对性能影响较小。
4. 并发清除与内存分配
清除阶段是并发的,在分配内存时逐步回收。Go 采用 span 管理,内存分配器(mcache、mcentral、mheap)与 GC 紧密配合,使得清除工作与分配工作可以交错进行,进一步降低停顿。
GC 性能与调优
Go GC 的目标是在 低延迟(STW 通常在毫秒级)和 适度吞吐 之间取得平衡。Go 提供了一些环境变量和运行时参数来调整 GC 行为:
GOGC:默认值为 100,表示当堆内存增长到上次 GC 后存活内存的 100%(即翻倍)时触发 GC。调高可以减少 GC 频率但增加停顿时间,调低则相反。GODEBUG=gctrace=1:可以输出 GC 详细日志,帮助分析。- 通过
runtime.ReadMemStats或pprof可监控 GC 指标。
对于大多数应用,默认的 GC 设置已经足够。如果遇到延迟敏感场景(如交易系统、游戏服务),可以通过 减少内存分配、复用对象(sync.Pool)、调高 GOGC 或 显式调用 runtime.GC() 等手段优化。
为什么 Go 的 GC 适合服务器场景
- 低 STW:并发标记和清除使得停顿时间很短,通常在微秒到毫秒级。
- 吞吐与延迟平衡:通过调节 GOGC 可以在两者之间做权衡。
- 与调度器融合:后台标记 worker 与 goroutine 调度器协同,充分利用多核。
- 无需手动内存管理:在保证高性能的同时降低开发复杂度。
一句话总结
Go 的 GC 是一种并发的三色标记-清除垃圾回收器,通过混合写屏障实现低停顿的自动内存管理,配合调度器与内存分配器,使得编写高并发服务时既能享受自动内存管理的便利,又能获得接近手工内存管理的性能。