GC
Golang GC 的触发时机
Golang 的 GC 有两种触发方式:
- 主动触发: 通过调用 runtime.GC() 函数来手动触发 GC。这种方式会阻塞当前程序,直到 GC 完成。
- 被动触发: Golang 的 GC 会根据以下两种机制自动触发:
- 内存使用阈值触发: 当堆内存使用量达到上次 GC 后存活对象内存的 2 倍时,会触发新一轮 GC。这个阈值可以通过环境变量 GOGC 来调整。
- 定时触发: 如果一段时间(默认 2 分钟)内没有触发过 GC,运行时会强制触发一次 GC。
Golang GC 的实现原理
Golang 的 GC 采用了三色标记清除算法:
- 标记阶段:
- 将所有根对象(全局变量、栈上对象等)标记为"灰色"并入队。
- 从灰色对象出发,递归地将其引用的对象标记为"黑色"。
- 在标记过程中,如果发现新的对象被引用,会将其标记为"灰色"并入队。
- 清除阶段:
- 将所有未被标记为"黑色"的对象(即"白色"对象)回收。
- 将回收的内存归还给操作系统。
- Golang 的 GC 实现中还引入了写屏障(write barrier)机制,用于在标记阶段检测对象引用关系的变化,确保标记的正确性。
总的来说,Golang 的 GC 机制通过自动触发和三色标记清除算法,能够高效地管理内存,减少内存碎片,提高程序的性能和稳定性。
什么时候触发 STW

- 标记准备阶段(Mark Setup):
时机:GC 周期开始时
目的: 启用写屏障
准备 GC 工作线程
扫描所有 goroutine 的栈
持续时间:通常在 10-30 微秒级别
- 标记终止阶段(Mark Termination):
时机:并发标记阶段结束后
目的: 重新扫描(Rescan)全局队列和 P 本地队列中的对象
执行一些清理操作
关闭写屏障
持续时间:通常比标记准备阶段稍长,但仍在毫秒级别以下
非 GC 周期触发的 STW
-
全局内存回收:
时机:当需要将内存返还给操作系统时
目的:确保安全地释放不再使用的内存页
频率:相对较低,通常在程序空闲时进行 -
栈收缩(Stack Shrinking):
时机:在 GC 周期结束后,如果发现某些 goroutine 的栈使用率低
目的:减小栈的大小,释放未使用的内存
注意:这个操作通常与 GC 结合进行,以减少额外的 STW -
大对象分配:
时机:当程序尝试分配一个非常大的对象时
目的:确保有足够的连续内存空间
频率:相对较低,取决于程序的内存使用模式
STW 的触发机制
协作式抢占:Go 1.14 之前,STW 依赖于 goroutine 在特定检查点(如函数调用)自愿让出控制权 异步抢占:Go 1.14 及之后,引入了基于信号的异步抢占机制,使 STW 更加及时和可预测
减少 STW 影响的策略
-
增量 GC: Go 1.5 之后引入,将 GC 工作分散到多个小的增量中
减少了单次 STW 的持续时间 -
并行 GC: 利用多核并行执行 GC 工作
显著减少了 STW 的总时间 -
后台 GC: 在程序空闲时执行部分 GC 工作
减少了 GC 对程序性能的影响
STW 持续时间的演进
Go 1.5 之前:STW 时间可能达到几百毫秒
Go 1.5 - 1.12:STW 时间显著减少,通常在 10 毫秒以下
Go 1.13+:大多数情况下,STW 时间控制在 100 微秒量级
监控和调优
使用 GODEBUG=gctrace=1 环境变量可以查看详细的 GC 日志,包括 STW 时间
通过 pprof 工具分析 GC 性能
调整 GOGC 值可以影响 GC 频率,间接影响 STW 频率