学习Golang GC 必知的几个知识点

对于gc的介绍主要位于 src/runtime/mgc.go,以下内容是对注释的翻译。

GC 四个阶段

通过源文件注释得知GC共分四个阶段:

  1. GC 清理终止 (GC performs sweep termination
    a. Stop the world, 每个P 进入GC safepoint(安全点),从此刻开始,万物静止
    b. 清理未被清理的span,如果GC被强制执行时才会出现这些未清理的span
  2. GC 标记阶段(GC performs the mark phase
    a. 将gc标记从 _GCoff 修改为 _GCmark,开启写屏障(write barries)和 协助助手(mutator assists),将根对象放入队列。 在STW期间,在所有P都启用写屏障之前不会有什么对象被扫描。
    b. Start the world恢复STW)。标记工作线程和协助助手并发的执行。对于任何指针的写操作和指针值,都会被写屏障覆盖,使新分配的对象标记为黑色。
    c. GC 执行根标记工作。包括扫描所有的栈,全局对象和不在堆数据结构中的堆指针。每扫描一个栈就会导致goroutine停止,把在栈上找到的所有指针置灰色,然后再恢复goroutine运行。
    d. GC 遍历队列中的每个灰色对象,扫描完以后将灰色对象标记为黑色,并将其指向的对象标记为灰色。
    e. 由于GC工作在分布本地缓存中,采用了一种 “分布式终止算法(distributed termination algorithm)” 来检测什么时候没有根对象或灰色对象。在这个时机GC会转为标记中止(mark termination)。
  3. 标记终止(GC performs mark termination
    a. Stop the world从此刻开始,万物静止
    b. 设置阶段为 _GCmarktermination,并禁用 工作线程worker和协助助手
    c. 执行清理,flush cache
  4. 清理阶段(GC performs the sweep phase
    a. 设置清理阶段标记为 _GCoff,设置清理状态禁用写屏障
    b. Start the world恢复STW),从现在开始,新分配的对象是白色的。如有必要,请在请在使用前扫描清理
    c. GC在后台执行并发扫描,并响应分配

整个GC共四个阶段,每次开始时从上到下执行。第一步是清理上次未清理完的span,而不是直接标记阶段。具体的流程可以参考 runtime.GC() 函数

在每轮GC都会有两次STW发生,每个阶段都会发生一次STW相关的操作。

并发清理(Concurrent sweep)

清除阶段与程序执行是并发执行的。后台有一个goroutine并发的对heap进行清理,按span为是小单位一个接一个的清理。当 STW 标记终止后,所有的span将被标记为”needs seeping

后台goroutine进行清理时,是按span为单位,one-by-one 的执行。

为了避免当存在未清理的span时,应用会向系统申请更多的内存的问题,当一个goroutine需要另外span时(当前goroutine对应的P没有需要的span),它首先通过清理的方式获取相应的内存大小。当一个goroutine需要分配一些小对象 span 时,会先清理相同大小span ,直到至少释放了一个对象;当一个goroutine需要从heap上分配大对象时,它会清理span,直到至少释放一样大小的页到heap上。有一种情况例外:如果一个 goroutine 清理释放了两个不相邻的one-page span到堆上,将会导致重新分配一个新的two-page span,

GC速率(GC Rate)

下次GC是在我们分配了与已经使用的内存量成一定比例的之后。此值受 GOGC 环境变量控制,默认为100,即增涨率为100%。如果 GOGC=100 ,当前我们使用了4M,下次GC时将会在内存达到8M的时候发生(标记在 memstats.next_gc 变量)。这使GC成本保持线性与分配成本的比例。调整GOGC只会改变线性常数(以及使用的额外内存量)。

GC 注意事项

GC期间,将阻塞任何的调用,直到GC结束,即STW期间万物静止的状态,所以会阻塞整个程序的运行。

一次完整的GC包含 sweep termination, markmark terminationsweep 。对于 runtime.GC() 函数来说直到这四个阶段全部完成才会返回继续执行,否则程序将一直处于阻塞状态。对于要切换到 sweep 阶段,必须等待 mark termination 阶段全部完成才可以。

系统会记录GC的次数,这个值会在进入 sweep 阶段时更新+1。

对于触发GC 的几个条件请参考:https://blog.haohtml.com/archives/23911

GC 的三种模式

三种模式:

  • (1)gcBackgroundMode,默认模式,标记与清扫过程都是并发执行的;
  • (2)gcForceMode,只在清扫阶段支持并发;
  • (3)gcForceBlockMode,GC全程需要STW。
// gcMode indicates how concurrent a GC cycle should be.
type gcMode int

const (
	gcBackgroundMode gcMode = iota // concurrent GC and sweep
	gcForceMode                    // stop-the-world GC now, concurrent sweep
	gcForceBlockMode               // stop-the-world GC now and STW sweep (forced by user)
)