关于go的gc机制,我在公司的wiki中写了,有时间会贴出来的。
大体而言,go的垃圾回收是基于标记清除算法,其实现了三色标记以及内存屏障技术来实现并发的标记清除。
其主要阶段包括:
1.标记前的准备工作,包括清理上一次gc留下来的一些object,这是stw的
2.标记阶段,即对对象进行标记,这是借助于mspan中的sweepgen来确定该对象是否需要gc
3.标记结束,重新扫描部分根对象,STW
4.按标记结果进行清扫
关于标记清除后是否会进行整理,我个人理解是会的,因为在mheap中包含了所有的msapn形成了empty和noempty列表,应该会将清除后的mspan纳入到noempty,这里的empty和noempty应该指的是是否有空闲内存。
go的垃圾回收主要借助了两个技术,一个是三色标记,一个是内存屏障。
三色标记是指,使用白色,灰色和黑色来对对象进行标记,主要分为以下步骤:
1.首先将全部的对象标记成白色
2.将直接联系对象标记成灰色
3.将灰色对象的直接联系对象变成灰色,而将原来的灰色对象变成黑色
4.不断重复2,3过程
剩下的白色对象就是垃圾
但是相对应的,三色标记在与用户程序同时进行的过程中会有问题,如当gc扫描过对象A后,开始扫描对象B,并且对象B引用对象C,但这时,对象A对对象C建立了引用,对象B对对象C删除了引用,那么对象C会被当成垃圾而回收。内存屏障便是为了解决这种问题。内存屏障主要分为读屏障和写屏障,而由于读屏障需要在程序中额外添加代码,所以基本不用;写屏障类似于钩子函数,当在对对象进行写操作的时候,便会进行相应的操作,写屏障又分为插入写和删除写,具体就不再介绍。
介绍一下gc的源码:
1.gcstrat
位于mgc.go,1206行
func gcStart(trigger gcTrigger) {
//mp类似于一个锁,判断当前g是否可以抢占
mp := acquirem()
if gp := getg(); gp == mp.g0 || mp.locks > 1 || mp.preemptoff != "" {
releasem(mp)
return
}
releasem(mp)
mp = nil
//清扫残留垃圾,STW
for trigger.test() && sweepone() != ^uintptr(0) {
sweep.nbgsweep++
}
// Perform GC initialization and the sweep termination
// transition.
semacquire(&work.startSema)
// Re-check transition condition under transition lock.
if !trigger.test() {
semrelease(&work.startSema)
return
}
// 判断是否被强制执行
work.userForced = trigger.kind == gcTriggerCycle
//设置gc mode,应该类似于mspan中
mode := gcBackgroundMode
if debug.gcstoptheworld == 1 {
mode = gcForceMode
} else if debug.gcstoptheworld == 2 {
mode = gcForceBlockMode
}
// 开始做,获得当前状态
semacquire(&worldsema)
if trace.enabled {
traceGCStart()
}
// Check that all Ps have finished deferred mcache flushes.
for _, p := range allp {
if fg := atomic.Load(&p.mcache.flushGen); fg != mheap_.sweepgen {
println("runtime: p", p.id, "flushGen", fg, "!= sweepgen", mheap_.sweepgen)
throw("p mcache not flushed")
}
}
//启动后台标记任务
gcBgMarkStartWorkers()
//重置gc,标记相关状态
systemstack(gcResetMarkState)
work.stwprocs, work.maxprocs = gomaxprocs, gomaxprocs
if work.stwprocs > ncpu {
// This is used to compute CPU time of the STW phases,
// so it can't be more than ncpu, even if GOMAXPROCS is.
work.stwprocs = ncpu
}
work.heap0 = atomic.Load64(&memstats.heap_live)
work.pauseNS = 0
work.mode = mode
now := nanotime()
work.tSweepTerm = now
work.pauseStart = now
if trace.enabled {
traceGCSTWStart(1)
}
//正式开始,STW
systemstack(stopTheWorldWithSema)
// Finish sweep before we start concurrent scan.
systemstack(func() {
finishsweep_m()
})
// 清理 sync.pool sched.sudogcache、sched.deferpool
clearpools()
work.cycles++
gcController.startCycle()
work.heapGoal = memstats.next_gc
// In STW mode, disable scheduling of user Gs. This may also
// disable scheduling of this goroutine, so it may block as
// soon as we start the world again.
if mode != gcBackgroundMode {
schedEnableUser(false)
}
//设置gc状态为gcmark
setGCPhase(_GCmark)
// 更新 bgmark 的状态
gcBgMarkPrepare()
// 计算并排队root 扫描任务,并初始化相关扫描任务状态
gcMarkRootPrepare()
// 标记 tiny 对象
gcMarkTinyAllocs()
// 设置 gcBlackenEnabled 为 1,启用写屏障
atomic.Store(&gcBlackenEnabled, 1)
// Assists and workers can start the moment we start
// the world.
gcController.markStartTime = now
// Concurrent mark.
systemstack(func() {
now = startTheWorldWithSema(trace.enabled)
work.pauseNS += now - work.pauseStart
work.tMark = now
})
// In STW mode, we could block the instant systemstack
// returns, so don't do anything important here. Make sure we
// block rather than returning to user code.
if mode != gcBackgroundMode {
Gosched()
}
semrelease(&work.startSema)
}