Sirius
Sirius

目录

golang 垃圾回收

GC使用的方法是并发三色标记加上混合写屏障

  • 堆内存增长:当堆内存使用量达到上次GC后的2倍时触发(默认配置为100%)
  • 定时触发:默认2分钟强制触发一次GC
  • 手动触发:调用runtime.GC()

Go使用三色并发标记清除算法

  • 白色:未被访问的对象,GC结束后没有变色会被回收
  • 灰色:已被访问, 但其引用对象还未扫描完的对象
  • 黑色:已被访问 , 且其所有引用对象都已扫描的对象
  • 停止所有goroutine(Stop The World)
  • 启动后台标记工作goroutine
  • 将根对象(全局变量、栈变量等)标记为灰色
  • 恢复用户程序执行

  • 后台goroutine并发标记:

    • 从灰色对象队列中取出对象
    • 将其引用的白色对象标记为灰色
    • 将当前对象标记为黑色
  • 使用写屏障保证并发安全

  • 写屏障包含插入写屏障和删除写屏障

  • 插入写屏障(Dijkstra)的目标是实现强三色不变式,保证当一个黑色对象指向一个白色对象前,会先触发屏障将白色对象置为灰色,再建立引用.

  • 删除写屏障(Yuasa barrier)的目标是实现弱三色不变式,保证当一个白色对象即将被上游删除引用前,会触发屏障将其置灰,之后再删除上游指向其的引用.

  • 屏障机制类似于一个回调保护机制,指的是在完成某个特定动作前,会先完成屏障成设置的内容.

  • • 堆对象正常启用插入写屏障

  • • 堆对象正常启用删除写屏障

  • 屏障机制无法作用于栈对象.

  • GC期间新分配的对象会直接设置为黑色

会遍历释放所有P的写屏障缓存,查看是否存在因屏障机制遗留的灰色对象,如果有,则会推出gcMarkDone方法,回退到gcBgMarkWorker的主循环中,继续完成标记任务. 倘若写屏障中也没有遗留的灰对象,此时会调用STW停止世界,并步入gcMarkTermination方法进入标记终止阶段.

  • 再次停止所有goroutine
  • 完成剩余的标记工作
  • 清理写屏障
  • 并发清除所有白色对象
  • 回收内存空间

GC 优化

GOGC 是控制 GC 频率最重要的环境变量。它的默认值是 100。 它的含义是:当新分配的内存达到上次 GC 结束后存活内存的 GOGC% 时,触发下一次 GC

  • GOGC = 100 (默认): 意味着当堆内存增长到上一次的两倍时,触发 GC。这是一个在 CPU 开销和内存占用之间的平衡选择。
  • GOGC < 100: 会让 GC 更频繁地触发。这会增加 CPU 的消耗,但能更有效地控制内存占用,适合内存敏感的应用。
  • GOGC > 100: 会让 GC 不那么频繁。这会减少 CPU 的消耗,但会允许程序占用更多内存。
  • GOGC = off: 关闭 GC。极不推荐,除非是短时运行、内存占用明确的特殊程序。

你也可以在代码中通过 debug.SetGCPercent() 来动态设置。

从 Go 1.19 开始,引入了 GOMEMLIMIT 环境变量。它提供了一个软内存限制。当程序的总内存使用接近这个限制时,GC 会被更积极地触发,以尝试将内存维持在这个限制之下。这对于在有严格内存限制的容器环境中运行 Go 程序非常有用。

  1. 内存分配优化:最高效的 GC 就是不发生 GC。通过优化代码,减少不必要的临时对象分配,是性能调优的第一步。
    • 可以使用 sync.Pool 来复用对象。
    • 尽量预分配内存,减少动态扩容
  2. 避免指针:减少指针数量可降低GC扫描成本,GC 只关心指针指向的对象。如果你的数据结构中不包含指针(或 slice, map, chan 等),GC 的扫描工作量就会大大减少。
  3. 合理设置 GOGC:通过性能分析(pprof),观察应用的 GC 行为和内存增长情况,调整 GOGC 以在 CPU 和内存之间找到最佳平衡点。