返回

Go八股之GC

什么是 GC

垃圾回收(Garbage Collection,GC)是一种自动内存管理机制。

现代高级编程语言管理内存有两种方式:

  • 手动:C/C++,Rust等,需要主动申请或者释放内存
  • 自动:Java,Golang等,有内存分配器和垃圾收集器自动分配和回收内存

程序中会使用两种内存:

  • 堆内存:程序共享的内存,由 GC 进行回收
  • 栈内存:线程专用的内存,存储函数的参数值、局部变量等,由操作系统自动分配释放

Go 的垃圾回收

GC 相关术语

  • STW:全称 stop the word,GC 期间某个阶段会停止所有的赋值器,中断程序逻辑,以确定引用关系。
  • root 对象:根对象是指赋值器不需要通过其他对象就可以直接访问到的对象

GC V1.3

1.3 版本 Go 使用标记-清除作为垃圾回收算法:

  1. 开启STW,停止程序的运行
  2. 从根节点遍历,标记找出的所有可达对象
  3. 清理未标记的对象
  4. 继续运行程序

重复循环上述 1-4 步,直到 process 程序生命周期结束。标记清除法的最大弊端就是在整个GC期间需要STW,将整个程序暂停。因为如果不进行STW的话,会出现已经被标记的对象A,引用了新的未被标记的对象B,但由于对象A已经标记过了,不会再重新扫描A对B的可达性,从而将B对象当做垃圾回收掉。

GC V1.5

1.5 版本使用标记-清除算法,结合三色标记,插入写屏障

  1. 创建白、灰、黑三个集合
  2. 将所有对象先放入白色集合中
  3. 遍历所有 root 对象,把遍历到的对象从白色集合放入灰色集合(这里放入灰色集合的都是根节点直接可达的对象)
  4. 遍历灰色集合,将灰色对象直接可达的对象从白色集合放入灰色集合,自身标记为黑色
  5. 重复第四步,直至灰色集合中没有任何对象
  6. 回收白色集合内的所有对象

对于上述的算法来讲,仍然需要依赖STW的。因为如果不暂停程序, 程序的逻辑改变对象引用关系, 这种动作如果在标记阶段做了修改,会影响标记结果的正确性。

在三色标记法过程中对象丢失需要同时满足两个条件:

  • 条件一:白色对象被黑色对象引用
  • 条件二:灰色对象和白色对象之间的可达关系遭到破坏

Go 团队提出两个解决方案

  • 强三色不变式:不允许黑色对象引用白色对象,破坏条件一
  • 弱三色不变式:黑色对象可以引用白色对象,但是白色对象的上游必须存在灰色对象,破坏条件二

Go 团队基于上述不变式提出两种实现机制:

  • 插入写屏障(满足强三色不变式)
    • 规则:当一个对象引用另一个对象时,将另一个对象标记为灰色
    • 劣势:无法管理栈操作,需要 STW 重新扫描栈
  • 删除写屏障(满足弱三色不变式)
    • 规则:在删除引用时,如果被删除引用的对象自身为灰色或者白色,那么会标记为灰色
    • 劣势:冗余扫描成本高,回收精度低

GC V1.8

1.8 版本使用标记-清除算法,结合三色标记法和混合写屏障

  1. GC 开始将栈上的所有可达对象均标记为黑色
  2. GC 期间,任何在栈上创建的新对象,均为黑色
  3. 堆上被删除对象标记为灰色
  4. 堆上被添加的对象标记为灰色

GC 的触发时间

  • 主动触发:调用 runtime.GC() 方法
  • 被动触发:
    • 定时触发,该触发条件由 runtime.forcegcperiod 变量控制,默认为 2 分 钟。当超过两分钟没有产生任何 GC 时,触发 GC
    • 根据内存分配阈值触发,该触发条件由环境变量 GOGC 控制,默认值为100(100%),当前堆内存占用是上次 GC 结束后占用内存的 2 倍时,触发 GC

GC 调优

  • 控制内存分配的速度,限制Goroutine的数量,提高赋值器mutator的CPU利用率(降低GC的CPU利用率)
  • 少量使用+连接string
  • slice提前分配足够的内存来降低扩容带来的拷贝
  • 避免map key对象过多,导致扫描时间增加
  • 变量复用,减少对象分配,例如使用sync.Pool来复用需要频繁创建临时对象,使用全局变量
  • 增大GOGC的值,降低GC的运行频率
Licensed under CC BY-NC-SA 4.0
载入天数...载入时分秒...
使用 Hugo 构建
主题 StackJimmy 设计