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)的错误?如果不是, 那么你是否发现程序越来越慢, 并最终死掉, 尽管仍然还有足够的内存可用?
 - 并发:回收器是如何利用多核机器的?
 - 伸缩:当堆内存变大时, 回收器该如何工作?
 - 调优:回收器的默认使用或在进行调优时, 它的配置有多复杂?
 - 预热时间:回收算法是否会根据已发生的行为进行自我调节?如果是, 需要多长时间?
 - 页释放:回收算法会把未使用的内存释放回给操作系统吗?如果会, 会在什么时候发生?
什么是三色标记❓
 
- 有黑、白、灰三个集合,初始状态下所有对象都为白色;
 - 从 root 对象开始,将所有可达(直接引用的)对象都标记为灰色;
 - 从灰色对象集中取出对象,将其所有可达对象标记为灰色后,将自身标记为黑色;
 - 重复第三步,知道灰色集合为空,即所有所有被 root 引用或间接引用的对象都标记为黑色;
 - 此时,白色集合中的所有对象都为不可达的垃圾,对内存进行迭代清扫,回收白色对象;
 - 重置 GC 状态
 
什么是写屏障❓
三色标记需要维护不变性条件:黑色对象不能引用无法被灰色对象可达的白色对象。在并发标记时如果没有正确性保障措施,可能会漏标记对象,导致实际上可达的对象被错误的清扫掉。为了解决这个问题,go 使用了写屏障。
写屏障是在写入指针前执行的一小段代码,用来防止并发标记时指针丢失,这段代码 Go 是在编译时加入的。
var obj1 *Objectvar obj2 *Objecttype Object struct {data interface{}}func (obj *Object) Demo(){// 初始化obj1 = nilobj2 = obj// GC 垃圾回收开始工作// 扫描对象 obj1// 对象重新赋值obj1 = objobj2 = nil//扫描对象 obj2}
GC 执行流程
GC的触发机制如下:
- gcTriggerHeap:在分配内存时,当前已分配内存与上次 GC 结束时存活对象的内存达到某个比例时,触发 GC;
 - gcTriggerTime:由sysmon监测 2min 内是否执行过 GC,若没执行过,则执行 GC;
 - gcTriggerAlways:runtime.GC( ) 强制触发 GC;
1. 启动
在未对象分配内存后,mallocgc函数会检查垃圾回收触发条件,并按相关状态启动。垃圾回收默认是全并发模式,GC goroutine一直循环执行,直到符合触发条件时被唤醒。// path: go sdk/src/runtime/malloc.gofunc mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer
2. 标记
 
扫描,遍历相关内存区域,依次按照指针标记找出灰色可达对象,加入队列;
// path: go sdk/src/runtime/mgcmark.gofunc markroot(gcw *gcWork, i uint32)func scanblock(b0, n0 uintptr, ptmark *uint8, gcw *gcWork)
标记,将灰色对象从队列中取出,将其引用的对象标记为灰色,将自身标记为黑色;
// path: go sdk/src/runtime/mgc.gofunc gcBgMarkStartworkers()
3. 清理
所有白色标记的对象为不可达对象,将其内存回收。
并发清理本质上就是一个死循环,被唤醒后开始执行清理任务,完成内存回收操作后再次睡眠,知道下次执行任务。
// path: go sdk/src/runtime/mgcsweep.govar sweep sweepdata// 并发清理状态type sweepdata struct {lock mutexg *gparked boolstarted boolnbgsweep uint32npausesweep uint32}func bgsweep(c chan int)func sweepone() uintptr
