当堆上分配大于32k的对象的时候开始检测是否满足GC条件,满足则开始自动GC。
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
...
shouldhelpgc := false
// 分配的对象小于 32K byte
if size <= maxSmallSize {
...
} else {
shouldhelpgc = true
...
}
...
// gcShouldStart() 函数进行触发条件检测
if shouldhelpgc && gcShouldStart(false) {
// gcStart() 函数进行垃圾回收
gcStart(gcBackgroundMode, false)
}
}
另外,还可以通过调用runtime.GC()进行主动垃圾回收,主动垃圾回收的过程是主动式的。
// GC runs a garbage collection and blocks the caller until the
// garbage collection is complete. It may also block the entire
// program.
func GC() {
gcStart(gcForceBlockMode, false)
}
此外,Golang本身也会对运行状态监控,如果超过2分钟没有Gc,则触发GC。监控函数会在主goroutine中启动
初始化的时候会设置一个gc的触发阈值,当堆上的活跃对象大于阈值的时候则会触发GC
三色标记法的主要流程:
1、开始时所有对象都为白色。
2、从root开始找可达对象,将其标记为灰色,放入待处理队列。
3、遍历灰色对象队列,灰色对象的引用对象标记为灰色放入待处理队列,同时将自身标为黑色。
4、遍历完灰色队列对象后,清扫白色标记的对象。
三色标记法类似于标记清扫算法。但三色标记的好处在于可以让用户程序和Mark并发执行。
在go中,灰色对象队列使用gcw来管理灰色对象。gcw的结构体是gcWork,gcWork的核心在wbuf1和wbuf2,这里存储的就是灰色对象。
至于为什么采用2个buf来做缓冲呢,官方是这么解释的:
This can be thought of as a stack of both work buffers' pointers concatenated. When we pop the last pointer, we shift the stack up by one work buffer by bringing in a new full buffer and discarding an empty one. When we fill both buffers, we shift the stack down by one work buffer by bringing in a new empty buffer and discarding a full one. This way we have one buffer's worth of hysteresis, which amortizes the cost of getting or putting a work buffer over at least one buffer of work and reduces contention on the global work lists.
大致意思就是可以认为这2个buf是一个串联在一起的堆栈。在使用的时候就可以分摊开销程本,并且可以减少在global work lists上的竞争。
type p struct {
...
gcw gcWork
}
type gcWork struct {
// wbuf1 and wbuf2 are the primary and secondary work buffers.
wbuf1, wbuf2 wbufptr
// Bytes marked (blackened) on this gcWork. This is aggregated
// into work.bytesMarked by dispose.
bytesMarked uint64
// Scan work performed on this gcWork. This is aggregated into
// gcController by dispose and may also be flushed by callers.
scanWork int64
}
1、STW phase 1
这个阶段主要是在GC开始前做准备工作。
func gcStart(mode gcMode, forceTrigger bool) {
//在后台启动 mark worker
if mode == gcBackgroundMode {
gcBgMarkStartWorkers()
}
...
if mode == gcBackgroundMode {
// GC 开始前的准备工作
//处理设置 GCPhase,setGCPhase 会启动 write barrier
setGCPhase(_GCmark)
gcBgMarkPrepare() // Must happen before assist enable.
gcMarkRootPrepare()
// Mark all active tinyalloc blocks. Since we're
// allocating from these, they need to be black like
// other allocations. The alternative is to blacken
// the tiny block on every allocation from it, which
// would slow down the tiny allocator.
gcMarkTinyAllocs()
// Start The World
systemstack(startTheWorldWithSema)
} else {
...
}
}
2、Mark
这个阶段是并行的。从程序运行后一直在后台待着,大部分时候是在休眠状态,在gcstart()被调用后gcBgMarkWorker会被启动。
// mark 过程
systemstack(func() {
.
// Mark our goroutine preemptible so its stack
// can be scanned. This lets two mark workers
// scan each other (otherwise, they would
// deadlock). We must not modify anything on
// the G stack. However, stack shrinking is
// disabled for mark workers, so it is safe to
// read from the G stack.
casgstatus(gp, _Grunning, _Gwaiting)
switch _p_.gcMarkWorkerMode {
default:
throw("gcBgMarkWorker: unexpected gcMarkWorkerMode")
case gcMarkWorkerDedicatedMode:
gcDrain(&_p_.gcw, gcDrainNoBlock|gcDrainFlushBgCredit)
case gcMarkWorkerFractionalMode:
gcDrain(&_p_.gcw, gcDrainUntilPreempt|gcDrainFlushBgCredit)
case gcMarkWorkerIdleMode:
gcDrain(&_p_.gcw, gcDrainIdle|gcDrainUntilPreempt|gcDrainFlushBgCredit)
}
casgstatus(gp, _Gwaiting, _Grunning)
})
从这部分可以看出,当MarkWorker被启动后,会调用gcDrain()函数,由gcDrain实现Mark.
// gcDrain scans roots and objects in work buffers, blackening grey
// objects until all roots and work buffers have been drained.
func gcDrain(gcw *gcWork, flags gcDrainFlags) {
...
// Drain root marking jobs.
if work.markrootNext < work.markrootJobs {
for !(preemptible && gp.preempt) {
job := atomic.Xadd(&work.markrootNext, +1) - 1
if job >= work.markrootJobs {
break
}
markroot(gcw, job)
if idle && po。llWork() {
goto done
}
}
}
// 处理 heap 标记
// Drain heap marking jobs.
for !(preemptible && gp.preempt) {
...
//从灰色列队中取出对象
var b uintptr
if blocking {
b = gcw.get()
} else {
b = gcw.tryGetFast()
if b == 0 {
b = gcw.tryGet()
}
}
if b == 0 {
// work barrier reached or tryGet failed.
break
}
//扫描灰色对象的引用对象,标记为灰色,入灰色队列
scanobject(b, gcw)
}
}
3、Mark termination(Stw phase2)
这个阶段在go的1.8版本后以及不会对goroutine stack进行re-scan了。
4、清扫
清扫有2种方式:阻塞式和并行式
阻塞就是正常跑, 并行式是用锁的方式实现的。
func gcSweep(mode gcMode) {
...
//阻塞式
if !_ConcurrentSweep || mode == gcForceBlockMode {
// Special case synchronous sweep.
...
// Sweep all spans eagerly.
for sweepone() != ^uintptr(0) {
sweep.npausesweep++
}
// Do an additional mProf_GC, because all 'free' events are now real as well.
mProf_GC()
mProf_GC()
return
}
// 并行式
// Background sweep.
lock(&sweep.lock)
if sweep.parked {
sweep.parked = false
ready(sweep.g, 0, true)
}
unlock(&sweep.lock)
}
go的内存管理都是基于span的,在标记时针对对象的span标记,清扫的时候扫描span,对没标记的span进行回收。