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 *Object
var obj2 *Object
type Object struct {
data interface{}
}
func (obj *Object) Demo(){
// 初始化
obj1 = nil
obj2 = obj
// GC 垃圾回收开始工作
// 扫描对象 obj1
// 对象重新赋值
obj1 = obj
obj2 = 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.go
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer
2. 标记
扫描,遍历相关内存区域,依次按照指针标记找出灰色可达对象,加入队列;
// path: go sdk/src/runtime/mgcmark.go
func markroot(gcw *gcWork, i uint32)
func scanblock(b0, n0 uintptr, ptmark *uint8, gcw *gcWork)
标记,将灰色对象从队列中取出,将其引用的对象标记为灰色,将自身标记为黑色;
// path: go sdk/src/runtime/mgc.go
func gcBgMarkStartworkers()
3. 清理
所有白色标记的对象为不可达对象,将其内存回收。
并发清理本质上就是一个死循环,被唤醒后开始执行清理任务,完成内存回收操作后再次睡眠,知道下次执行任务。
// path: go sdk/src/runtime/mgcsweep.go
var sweep sweepdata
// 并发清理状态
type sweepdata struct {
lock mutex
g *g
parked bool
started bool
nbgsweep uint32
npausesweep uint32
}
func bgsweep(c chan int)
func sweepone() uintptr