什么是 GC

GC(Garbage Collection,垃圾回收)是一种自动内存管理机制,用于自动识别并回收程序中不再使用的内存(即“垃圾”)。程序员无需手动 freedelete,GC 会在运行时找到这些不可达的对象并释放其占用的内存,避免内存泄漏和悬空指针。


Go 的 GC 机制

Go 的 GC 经历了多次演进,从最初的简单标记清除,到现在的 并发、三色标记、混合写屏障 的算法。当前版本(Go 1.20+)的核心特点如下:

1. 三色标记法(Tri-color Marking)

将内存中的对象分为三种颜色:

  • 白色:尚未被扫描的对象,可能是垃圾。回收结束后,所有白色对象都会被清除。
  • 灰色:已被标记但尚未扫描其引用的对象(即已经访问到,但它的子对象还没处理)。
  • 黑色:已被扫描且其引用的所有子对象都已被标记(即该对象是存活且已完全处理)。

过程

  1. 初始:所有对象都是白色。
  2. 根对象扫描:从根对象(栈、全局变量、寄存器等)出发,将其引用的对象标记为灰色,放入工作队列。
  3. 并发标记:不断从工作队列取出灰色对象,扫描其引用,将引用的白色对象标记为灰色,并将当前对象变为黑色。
  4. 重复直到没有灰色对象。
  5. 清除:所有白色对象即为不可达的垃圾,回收内存。

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.ReadMemStatspprof 可监控 GC 指标。

对于大多数应用,默认的 GC 设置已经足够。如果遇到延迟敏感场景(如交易系统、游戏服务),可以通过 减少内存分配复用对象(sync.Pool)、调高 GOGC显式调用 runtime.GC() 等手段优化。


为什么 Go 的 GC 适合服务器场景

  • 低 STW:并发标记和清除使得停顿时间很短,通常在微秒到毫秒级。
  • 吞吐与延迟平衡:通过调节 GOGC 可以在两者之间做权衡。
  • 与调度器融合:后台标记 worker 与 goroutine 调度器协同,充分利用多核。
  • 无需手动内存管理:在保证高性能的同时降低开发复杂度。

一句话总结

Go 的 GC 是一种并发的三色标记-清除垃圾回收器,通过混合写屏障实现低停顿的自动内存管理,配合调度器与内存分配器,使得编写高并发服务时既能享受自动内存管理的便利,又能获得接近手工内存管理的性能。