Golang1.17源码分析之GC-011
Golang1.17 学习笔记011
GC 目前采用的是三色标记清除法
三色:黑、灰、白
黑色:被引用,不清理,追踪完成,gcmarkBits 对应的位为 1
灰色:可能被引用,需要再次扫描,未追踪完成
白色:没有被引用,或尚未追踪,gcmarkBits 对应的位为 0
11.1 内存标记
span 中维护了一个个内存块,并由一个位图 allocBits 表示每个内存块的分配情况。
在 span 数据结构中还有另一个位图 gcmarkBits 用于标记内存块被引用情况
标记阶段对每块内存进行标记,有对象引用的的内存标记为 1,没有引用到的保持默认为 0.
allocBits 和 gcmarkBits 数据结构是完全一样的,标记结束就是内存回收,回收时将 allocBits 指向 gcmarkBits,
则代表标记过的才是存活的,gcmarkBits 则会在下次标记时重新分配内存
11.2 STW
STW(stop the world),就是停掉所有协程,专注于 GC
11.3 垃圾回收优化
- 写屏障
写屏障类似一种开关,在GC的特定时机开启,开启后指针传递时会把指针标记,即本轮不回收,下次GC时再确定
- 辅助 GC
为了防止内存分配过快,在 GC 执行过程中,如果 goroutine 需要分配内存,那么这个 goroutine 会
参与一部分 GC 的工作,即帮助 GC 做一部分工作,这个机制叫作 Mutator Assist
11.4 垃圾回收触发时机
- 内存分配达到阈值时进行触发
阀值 = 上次GC内存分配量 * 内存增长率
内存增长率由环境变量 GOGC 控制,默认为 100,即每当内存扩大一倍时启动 GC
- 定期触发
默认情况下,最长 2 分钟触发一次 GC,在 src/runtime/proc.go:forcegcperiod 变量中被声明:
var forcegcperiod int64 = 2 * 60 * 1e9
- 手动触发:runtime.GC()
11.5 核心源码
源码包:runtime/mgc.go
func GC() {
// We consider a cycle to be: sweep termination, mark, mark
// termination, and sweep. This function shouldn't return
// until a full cycle has been completed, from beginning to
// end. Hence, we always want to finish up the current cycle
// and start a new one. That means:
//
// 1. In sweep termination, mark, or mark termination of cycle
// N, wait until mark termination N completes and transitions
// to sweep N.
//
// 2. In sweep N, help with sweep N.
//
// At this point we can begin a full cycle N+1.
//
// 3. Trigger cycle N+1 by starting sweep termination N+1.
//
// 4. Wait for mark termination N+1 to complete.
//
// 5. Help with sweep N+1 until it's done.
//
// This all has to be written to deal with the fact that the
// GC may move ahead on its own. For example, when we block
// until mark termination N, we may wake up in cycle N+2.
// Wait until the current sweep termination, mark, and mark
// termination complete.
n := atomic.Load(&work.cycles)
gcWaitOnMark(n)
// We're now in sweep N or later. Trigger GC cycle N+1, which
// will first finish sweep N if necessary and then enter sweep
// termination N+1.
gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1})
// Wait for mark termination N+1 to complete.
gcWaitOnMark(n + 1)
// Finish sweep N+1 before returning. We do this both to
// complete the cycle and because runtime.GC() is often used
// as part of tests and benchmarks to get the system into a
// relatively stable and isolated state.
for atomic.Load(&work.cycles) == n+1 && sweepone() != ^uintptr(0) {
sweep.nbgsweep++
Gosched()
}
// Callers may assume that the heap profile reflects the
// just-completed cycle when this returns (historically this
// happened because this was a STW GC), but right now the
// profile still reflects mark termination N, not N+1.
//
// As soon as all of the sweep frees from cycle N+1 are done,
// we can go ahead and publish the heap profile.
//
// First, wait for sweeping to finish. (We know there are no
// more spans on the sweep queue, but we may be concurrently
// sweeping spans, so we have to wait.)
for atomic.Load(&work.cycles) == n+1 && !isSweepDone() {
Gosched()
}
// Now we're really done with sweeping, so we can publish the
// stable heap profile. Only do this if we haven't already hit
// another mark termination.
mp := acquirem()
cycle := atomic.Load(&work.cycles)
if cycle == n+1 || (gcphase == _GCmark && cycle == n+2) {
mProf_PostSweep()
}
releasem(mp)
}