golang 垃圾回收
目录
GC使用的方法是并发三色标记加上混合写屏障
1 1. GC触发条件
- 堆内存增长:当堆内存使用量达到上次GC后的2倍时触发(默认配置为100%)
- 定时触发:默认2分钟强制触发一次GC
- 手动触发:调用
runtime.GC()
2 2. 三色标记算法
Go使用三色并发标记清除算法:
- 白色:未被访问的对象,GC结束后没有变色会被回收
- 灰色:已被访问, 但其引用对象还未扫描完的对象
- 黑色:已被访问 , 且其所有引用对象都已扫描的对象
3 3. GC执行流程
3.1 Phase 1: Mark Setup (STW)
- 停止所有goroutine(Stop The World)
- 启动后台标记工作goroutine
- 将根对象(全局变量、栈变量等)标记为灰色
3.2 Phase 2: Concurrent Mark
-
恢复用户程序执行
-
后台goroutine并发标记:
- 从灰色对象队列中取出对象
- 将其引用的白色对象标记为灰色
- 将当前对象标记为黑色
-
使用写屏障保证并发安全
-
写屏障包含插入写屏障和删除写屏障
-
插入写屏障(Dijkstra)的目标是实现强三色不变式,保证当一个黑色对象指向一个白色对象前,会先触发屏障将白色对象置为灰色,再建立引用.
-
删除写屏障(Yuasa barrier)的目标是实现弱三色不变式,保证当一个白色对象即将被上游删除引用前,会触发屏障将其置灰,之后再删除上游指向其的引用.
-
屏障机制类似于一个回调保护机制,指的是在完成某个特定动作前,会先完成屏障成设置的内容.
-
• 堆对象正常启用插入写屏障
-
• 堆对象正常启用删除写屏障
-
屏障机制无法作用于栈对象.
-
GC期间新分配的对象会直接设置为黑色
3.3 Phase 3: Mark Termination (STW)
会遍历释放所有P的写屏障缓存,查看是否存在因屏障机制遗留的灰色对象,如果有,则会推出gcMarkDone方法,回退到gcBgMarkWorker的主循环中,继续完成标记任务. 倘若写屏障中也没有遗留的灰对象,此时会调用STW停止世界,并步入gcMarkTermination方法进入标记终止阶段.
- 再次停止所有goroutine
- 完成剩余的标记工作
- 清理写屏障
3.4 Phase 4: Sweep
- 并发清除所有白色对象
- 回收内存空间
GC 优化
0.4.1 GOGC
:核心调优参数
GOGC
是控制 GC 频率最重要的环境变量。它的默认值是 100
。 它的含义是:当新分配的内存达到上次 GC 结束后存活内存的 GOGC
% 时,触发下一次 GC。
GOGC = 100
(默认): 意味着当堆内存增长到上一次的两倍时,触发 GC。这是一个在 CPU 开销和内存占用之间的平衡选择。GOGC < 100
: 会让 GC 更频繁地触发。这会增加 CPU 的消耗,但能更有效地控制内存占用,适合内存敏感的应用。GOGC > 100
: 会让 GC 不那么频繁。这会减少 CPU 的消耗,但会允许程序占用更多内存。GOGC = off
: 关闭 GC。极不推荐,除非是短时运行、内存占用明确的特殊程序。
你也可以在代码中通过 debug.SetGCPercent()
来动态设置。
0.4.2 GOMEMLIMIT
:软内存限制
从 Go 1.19 开始,引入了 GOMEMLIMIT
环境变量。它提供了一个软内存限制。当程序的总内存使用接近这个限制时,GC 会被更积极地触发,以尝试将内存维持在这个限制之下。这对于在有严格内存限制的容器环境中运行 Go 程序非常有用。
0.4.3 优化方法
- 内存分配优化:最高效的 GC 就是不发生 GC。通过优化代码,减少不必要的临时对象分配,是性能调优的第一步。
- 可以使用
sync.Pool
来复用对象。 - 尽量预分配内存,减少动态扩容
- 可以使用
- 避免指针:减少指针数量可降低GC扫描成本,GC 只关心指针指向的对象。如果你的数据结构中不包含指针(或
slice
,map
,chan
等),GC 的扫描工作量就会大大减少。 - 合理设置
GOGC
:通过性能分析(pprof),观察应用的 GC 行为和内存增长情况,调整GOGC
以在 CPU 和内存之间找到最佳平衡点。