Go 垃圾回收(三)——三色标记法是什么鬼?

前言

当我们说 Go 的垃圾回收的时候,通常会提到三色标记法。有的同学可能听到算法里有颜色就会开始慌,因为另一个同样有颜色的算法就是红黑树。红黑树的各种规则各种旋转让人各种头疼,现在更可怕的是三色标记法竟然有三种颜色。那么事实上真的这样吗?并不是,颜色多不一定难,因为这个三色标记法一分钟就可以学会了。

追踪式垃圾回收Tracing garbage collection)

主流的两类垃圾回收算法有两种,分别是追踪式垃圾回收算法[1]和引用计数法( Reference counting )。而三色标记法是属于追踪式垃圾回收算法的一种。

追踪式算法的核心思想是判断一个对象是否可达,因为一旦这个对象不可达就可以立刻被 GC 回收了。那么我们怎么判断一个对象是否可达呢?很简单,第一步找出所有的全局变量和当前函数栈里的变量,标记为可达。第二步,从已经标记的数据开始,进一步标记它们可访问的变量,以此类推,专业术语叫传递闭包。

为什么需要三色标记法?

在三色标记法之前有一个算法叫 Mark-And-Sweep(标记清扫),这个算法就是严格按照追踪式算法的思路来实现的。这个算法会设置一个标志位来记录对象是否被使用。最开始所有的标记位都是 0,如果发现对象是可达的就会置为 1,一步步下去就会呈现一个类似树状的结果。等标记的步骤完成后,会将未被标记的对象统一清理,再次把所有的标记位设置成 0 方便下次清理。

这个算法最大的问题是 GC 执行期间需要把整个程序完全暂停,不能异步进行 GC 操作。因为在不同阶段标记清扫法的标志位 0 和 1 有不同的含义,那么新增的对象无论标记为什么都有可能意外删除这个对象。对实时性要求高的系统来说,这种需要长时间挂起的标记清扫法是不可接受的。所以就需要一个算法来解决 GC 运行时程序长时间挂起的问题,那就三色标记法。

三色标记法好在哪里?

相比传统的标记清扫算法,三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个 GC。

三色标记法很简单[2]。首先将对象用三种颜色表示,分别是白色、灰色和黑色。最开始所有对象都是白色的,然后把其中全局变量和函数栈里的对象置为灰色。第二步把灰色的对象全部置为黑色,然后把原先灰色对象指向的变量都置为灰色,以此类推。等发现没有对象可以被置为灰色时,所有的白色变量就一定是需要被清理的垃圾了。

三色标记法(来自维基百科)

三色标记法因为多了一个白色的状态来存放不确定的对象,所以可以异步地执行。当然异步执行的代价是可能会造成一些遗漏,因为那些早先被标记为黑色的对象可能目前已经是不可达的了。所以三色标记法是一个 false negative(假阴性)的算法。

除了异步标记的优点,三色标记法掌握了更多当前内存的信息,因此可以更加精确地按需调度,而不用像标记清扫法那样只能定时执行。

总结

所以三色标记法就是这么简单。在下一篇文章中我们会详细讲讲一次完整的垃圾回收过程,顺便解答一下在第一篇文章中埋下的问题——为什么那两个 goroutine 不能并发运行?

文章链接

Go 垃圾回收(一)——为什么要学习 GC ?

Go 垃圾回收(二)——垃圾回收是什么?

Go 垃圾回收(三)——三色标记法是什么鬼?

Go 垃圾回收(四)——一次完整的回收

参考

  1. ^追踪式回收算法 https://en.wikipedia.org/wiki/Tracing_garbage_collection
  2. ^三色标记法 https://en.wikipedia.org/wiki/Tracing_garbage_collection#Tri-color_marking
发布于 02-15