封面《pieces / 渡り鳥のソムニウム》

GO V1.3 标记 - 清除算法

此算法主要有两个主要的步骤:

  • 标记 (Mark phase)
  • 清除 (Sweep phase)

mark-sweep

  1. STW (stop the world), 找出不可达的对象,然后做上标记。
  2. 开始标记,程序找出它所有可达的对象,并做上标记。
  3. 标记完了之后,然后开始清除未标记的对象。
  4. 停止暂停,让程序继续跑。然后循环重复这个过程,直到 process 程序生命周期结束。

所以 Go V1.3 版本之前就是以上来实施的,在执行 GC 的基本流程就是首先启动 STW 暂停,然后执行标记,再执行数据回收,最后停止 STW
GOv1.3以前

Go V1.3 做了简单的优化,将 STW 提前,减少 STW 暂停的时间范围。将 STW 的步骤提前了一步,因为在 Sweep 清除的时候,可以不需要 STW 停止,因为这些对象已经是不可达对象了,不会出现回收写冲突等问题。
GOv1.3以后

缺点:

  • STW,stop the world;让程序暂停,程序出现卡顿 (重要问题)
  • 标记需要扫描整个 heap
  • 清除数据会产生 heap 碎片

无论如何 mark-sweep 都会暂停整个程序,为了解决这个问题在 GOv1.5 版本引入了三色并发标记法来优化

GO v1.5 三色标记法

Golang 中的垃圾回收主要应用三色标记法,GC 过程和其他用户 goroutine 可并发运行,但需要一定时间的 STW (stop the world)

定义

  • 黑色:检测到有被引用,并且已经遍历完它所有直接引用的对象或者属性
  • 白色:还没检测到有引用的对象(检测开始前,所有对象都是白色,检测结束后,没有被引用的对象都是白色,会被清查掉)
  • 灰色:检测到有被引用,但是他的属性还没有被遍历完,等遍历完后也会变成黑色

三色标记法流程

如果不使用 STW,这种方式会造成标记不准确的问题,下面两种情况是不希望发生的

  • 一个白色对象被黑色对象引用
  • 白色对象又被某个灰色(或者上级有灰色对象)对象取消引用。

没有STW的三色标记法

为了防止上面的现象发生,最简单的方式就是使用 STW,直接禁止掉其他用户程序对对象引用关系的干扰,但是 STW 的过程有明显的资源浪费,对所有的用户程序都有很大影响。因此 Go 的方法是使用插入屏障和删除屏障

屏障机制

” 强 - 弱 “三色不等式

  1. 强三色不等式

不存在黑色对象引用到白色对象的指针
强三色不变色实际上是强制性的不允许黑色对象引用白色对象,这样就不会出现有白色对象被误删的情况

强三色不等式

  1. 弱三色不等式

所有被黑色对象引用的白色对象都处于灰色保护状态
弱三色不变式强调,黑色对象可以引用白色对象,但是这个白色对象必须存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象。

弱三色不等式

插入屏障

具体操作: 在 A 对象引用 B 对象的时候,B 对象被标记为灰色。(将 B 挂在 A 下游,B 必须被标记为灰色)

满足: 强三色不变式. (不存在黑色对象引用白色对象的情况了, 因为白色会强制变成灰色)

色对象的内存槽有两种位置,栈和堆。栈空间的特点是容量小,但是要求相应速度快,因为函数调用弹出频繁使用,所以 “插入屏障” 机制,在栈空间的对象操作中不使用。而仅仅使用在堆空间对象的操作中
对于栈的部分使用 STW 保护

插入屏障

删除屏障

具体操作: 被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。

满足: 弱三色不变式. (保护灰色对象到白色对象的路径不会断)

这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮 GC 中被清理掉。

删除屏障

GO v1.8 混合写屏障 (hybrid write barrier) 机制

插入写屏障和删除写屏障的缺点

  • 插入写屏障:结束时需要 STW 来重新扫描栈,标记栈上引用的白色对象的存活;
  • 删除写屏障:回收精度低,GC 开始时 STW 扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。

Go V1.8 版本引入了混合写屏障机制(hybrid write barrier),避免了对栈 re-scan 的过程,极大的减少了 STW 的时间。结合了两者的优点。

混合写屏障

具体操作:

  1. GC 开始将栈上的对象全部扫描并标记为黑色 (之后不再进行第二次重复扫描,无需 STW),
  2. GC 期间,任何在栈上创建的新对象,均为黑色。
  3. 被删除的对象标记为灰色。
  4. 被添加的对象标记为灰色。

需要注意屏障技术是不在栈上应用的,因为要保证栈的运行效率
混合写屏障是 GC 的一种屏障机制,所以只是当程序执行 GC 的时候,才会触发这种机制。

Golang 中的混合写屏障满足弱三色不变式,结合了删除写屏障和插入写屏障的优点,只需要在开始时并发扫描各个 goroutine 的栈,使其变黑并一直保持,这个过程不需要 STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行 re-scan 操作了,减少了 STW 的时间

总结

GoV1.3- 普通标记清除法,整体过程需要启动 STW,效率极低。

GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈 (需要 STW),效率普通

GoV1.8 - 三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要 STW,效率较高。

参考

Golang 三色标记混合写屏障 GC 模式全分析

[典藏版] Golang 三色标记、混合写屏障 GC 模式图文全分析