GC(Garbage Collection、垃圾回收)是一种自动管理内存的方式,主要分为引用计数标记清除三色标记分代收集四类。支持 GC 的语言无需手动管理内存,程序后台会自动判断对象是否存活并回收为其分配的内存空间,使开发人员从内存管理上解脱出来。

Go GC 发展史

版本 发布时间 GC STW时间
v1.1 2013/5 STW 百毫秒~几百毫秒
v1.3 2014/6 Mark STW和Sweep并行 百毫秒
v1.5 2015/8 三色标记发、并发标记清除 10ms
v1.8 2017/2 hybird write barrier(混合写屏障) < 1ms

Go GC 特征

三色标记、并发标记和清扫、非分代、非紧缩、混合写屏障。

GC 关心的是什么❓

  • 程序吞吐量:回收算法会在多大程度上拖慢程序? 可以通过GC占用的CPU与其他CPU时间的百分比描述
  • GC吞吐量:在8给定的CPU时间内, 回收器可以回收多少垃圾?
  • 堆内存开销:回收器最少需要多少额外的内存开销?
  • 停顿时间:回收器会造成多大的停顿?
  • 停顿频率:回收器造成的停顿频率是怎样的?
  • 停顿分布:停顿有时候很长, 有时候很短? 还是选择长一点但保持一致的停顿时间?
  • 分配性能:新内存的分配是快, 慢还是无法预测?
  • 压缩:当堆内存里还有小块碎片化的内存可用时, 回收器是否仍然抛出内存不足(OOM)的错误?如果不是, 那么你是否发现程序越来越慢, 并最终死掉, 尽管仍然还有足够的内存可用?
  • 并发:回收器是如何利用多核机器的?
  • 伸缩:当堆内存变大时, 回收器该如何工作?
  • 调优:回收器的默认使用或在进行调优时, 它的配置有多复杂?
  • 预热时间:回收算法是否会根据已发生的行为进行自我调节?如果是, 需要多长时间?
  • 页释放:回收算法会把未使用的内存释放回给操作系统吗?如果会, 会在什么时候发生?

    什么是三色标记❓

  1. 有黑、白、灰三个集合,初始状态下所有对象都为白色;
  2. 从 root 对象开始,将所有可达(直接引用的)对象都标记为灰色;
  3. 从灰色对象集中取出对象,将其所有可达对象标记为灰色后,将自身标记为黑色;
  4. 重复第三步,知道灰色集合为空,即所有所有被 root 引用或间接引用的对象都标记为黑色;
  5. 此时,白色集合中的所有对象都为不可达的垃圾,对内存进行迭代清扫,回收白色对象;
  6. 重置 GC 状态

image.png

什么是写屏障❓

三色标记需要维护不变性条件:黑色对象不能引用无法被灰色对象可达的白色对象。在并发标记时如果没有正确性保障措施,可能会漏标记对象,导致实际上可达的对象被错误的清扫掉。为了解决这个问题,go 使用了写屏障。
写屏障是在写入指针前执行的一小段代码,用来防止并发标记时指针丢失,这段代码 Go 是在编译时加入的。

  1. var obj1 *Object
  2. var obj2 *Object
  3. type Object struct {
  4. data interface{}
  5. }
  6. func (obj *Object) Demo(){
  7. // 初始化
  8. obj1 = nil
  9. obj2 = obj
  10. // GC 垃圾回收开始工作
  11. // 扫描对象 obj1
  12. // 对象重新赋值
  13. obj1 = obj
  14. obj2 = nil
  15. //扫描对象 obj2
  16. }

image.png

GC 执行流程

GC的触发机制如下:

  • gcTriggerHeap:在分配内存时,当前已分配内存与上次 GC 结束时存活对象的内存达到某个比例时,触发 GC;
  • gcTriggerTime:由sysmon监测 2min 内是否执行过 GC,若没执行过,则执行 GC;
  • gcTriggerAlways:runtime.GC( ) 强制触发 GC;

    1. 启动

    在未对象分配内存后,mallocgc函数会检查垃圾回收触发条件,并按相关状态启动。垃圾回收默认是全并发模式,GC goroutine一直循环执行,直到符合触发条件时被唤醒。
    1. // path: go sdk/src/runtime/malloc.go
    2. func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer

    2. 标记

  1. 扫描,遍历相关内存区域,依次按照指针标记找出灰色可达对象,加入队列;

    1. // path: go sdk/src/runtime/mgcmark.go
    2. func markroot(gcw *gcWork, i uint32)
    3. func scanblock(b0, n0 uintptr, ptmark *uint8, gcw *gcWork)
  2. 标记,将灰色对象从队列中取出,将其引用的对象标记为灰色,将自身标记为黑色;

    1. // path: go sdk/src/runtime/mgc.go
    2. func gcBgMarkStartworkers()

    3. 清理

    所有白色标记的对象为不可达对象,将其内存回收。

    并发清理本质上就是一个死循环,被唤醒后开始执行清理任务,完成内存回收操作后再次睡眠,知道下次执行任务。

  1. // path: go sdk/src/runtime/mgcsweep.go
  2. var sweep sweepdata
  3. // 并发清理状态
  4. type sweepdata struct {
  5. lock mutex
  6. g *g
  7. parked bool
  8. started bool
  9. nbgsweep uint32
  10. npausesweep uint32
  11. }
  12. func bgsweep(c chan int)
  13. func sweepone() uintptr