golang垃圾回收机制
golang的GC,1.8通过混合写⼊屏障, 使得STW降到了sub ms。go语言中程序代码执行和垃圾回收是并发执行的。
当前Go GC特征 :
三色标记,并发标记和清扫,非分代,非紧缩,混合写屏障。
三色标记:
1. 将所有对象放在白色集合中
2. 从rootset遍历可达对象,将可达对象放在灰色集合中
3. 将灰色集合中对象进行遍历,将可达对象放在灰色集合中,将其本身放在黑色集合中
4. 重复第三步,直到灰色集合遍历为空
5. 将白色集合中的对象视为垃圾进行清扫
6. 重置黑色集合对象,将其变为白色,进行下次GC
gc写屏障解决垃圾回收并发的问题:
写屏障是在写入指针前执行的一小段代码,用以防止并发标记时指针丢失,这段代码Go是在编译时加上的。
如果以下代码没有写屏障,则当代码执行和垃圾回收并发执行时,GC垃圾回收扫描到obj1为nil,将其标记为回收对象,此时代码重新给obj1和obj2赋值,obj2为nil,垃圾回收将obj2标记为回收对象,此时obj1和obj2都会被回收,但obj1此时是不需要回收的
func (obj *Object) Demo() { //初始化 obj1 = nil obj2 = obj //gc 垃圾回收开始⼯作 //扫描对象 obj1 完成后 //代码修改为:对象重新赋值 obj1 = obj obj2 = nil //扫描对象 obj2 }
GC触发
1. 分配内存时, 当前已分配内存与上一次GC结束时存活对象的内存达到某个比例时就触发GC。
2. sysmon检测2min内是否运行过GC, 没运行过 则执行GC。
3. runtime.GC()强制触发GC。
垃圾回收默认是全并发模式运行,GC goroutine 一直循环执行,直到符合触发条件时被唤醒。
并发标记分为两个步骤:
扫描:遍历相关内存区域,依次按照指针标记找出灰色可达对象,加入队列。
标记:将灰色对象从队列取出,将其引用对象标记为灰色,自身标记为黑色。
// 将灰色对象从队列取出,其引用对象标灰,自身标黑 func gcBgMarkStartWorkers() { // Background marking is performed by per-P G's. Ensure that // each P has a background GC G. for _, p := range allp { if p.gcBgMarkWorker == 0 { go gcBgMarkWorker(p) notetsleepg(&work.bgMarkReady, -1) noteclear(&work.bgMarkReady) } } }
垃圾清理
所有未标记的白色对象不再被引用,可以将其内存回收。
// 无限循环,被唤醒后执行并发清理任务,完成后回收内存等待下次执行 func bgsweep(c chan int) { sweep.g = getg() lock(&sweep.lock) sweep.parked = true c <- 1 goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1) for { for sweepone() != ^uintptr(0) { sweep.nbgsweep++ Gosched() } for freeSomeWbufs(true) { Gosched() } lock(&sweep.lock) if !isSweepDone() { // This can happen if a GC runs between // gosweepone returning ^0 above // and the lock being acquired. unlock(&sweep.lock) continue } sweep.parked = true goparkunlock(&sweep.lock, waitReasonGCSweepWait, traceEvGoBlock, 1) } }