go语言的gc采用了三色标记法。
三色标记法将对象标记为黑、白、灰三种颜色,黑色对象为可达对象,应该保留,白色对象为不可达对象,应该被清除,灰色作为中间过度态。
下面以链表为例,介绍三色标记-清除法的过程。
-
假设我们新建了三个结点A、B、C,其中,A和B是头结点,C是B的下一个节点,一般头结点都是可达的。由于GC周期还没开始,所以这三个对象都属于白色集合。
-
接着我们新建节点D,并作为A的下一个节点。注意,此时D将被放置在灰色集合中!因为有这样一条规则:当一个指针域变化时,则该指针指向的对象需要变色。因为所有新建对象都需要将其地址赋值给一个引用,所以它们会立即变成灰色。
-
在Go中,GC进程和正常程序进程是并行的,它们在计算机中交替运行。此时GC过程开始运行,根对象A、B都被移到灰色集合中。
-
扫描内存对象,GC收集器会扫描灰色集合的对象,将扫描到的对象标记为黑色,并将其子对象标记为灰色。我们选择对象A进行扫描,将A移到黑色集合,由于D本身就在灰色集合,所以不用移动。无论哪个阶段,GC都可以计算出剩余对象的移动次数为
2×[白色集合对象数]+[灰色集合对象数]
。理解这个公式也不难,因为灰色对象最后要全部移到黑色集合中,白色对象要先移到灰色集合中,然后才能移入黑色集合,这就是系数2的由来。每次GC扫描都至少要进行一次移动,直到灰色集合中对象数为0,也就是剩余对象移动次数为0才罢休。
-
程序继续运行,我们又新建了对象E,并作为对象C的下一个节点,同样对象E也会被分配到灰色集合。这一步增加了GC阶段数,也就是GC扫描的次数,因此会导致GC清除阶段的推迟。
-
此时我们让B指向E,这样一来,对象C将变得不可达。按照第4步GC收集器扫描内存对象的过程,由于灰色集合中没有对象指向C,因此,对象C将永远残留在白色集合中,最后被GC收集器回收掉。
-
GC扫描继续进行,这一次扫描对象D。将对象D移到黑色集合中,但是D没有子对象了,所以直接将D移入黑色集合中就行了。
-
回到正常程序流程,我们将对象B的next指针置空,断开B与E的关联。按道理E也变为不可达了,应该被清除掉,但是E却在灰色集合中,下一次GC扫描就会被移入黑色集合,不会回到白色集合中,也就不能被回收了。确实是这样,这一轮的GC的确不能将E回收,但是没关系,下一轮的GC就可以将E回收了。
-
GC扫描继续运行,这一次扫描对象E,它也没有子对象,直接将它移入黑色集合就好。注意,这里并不会将对象C移入黑色集合,因为C是E的上层对象,而不是下层对象,简单的说,E的指针并没有指向C。
-
GC收集器扫描对象B,也是孤家寡人一个,直接移入黑色集合完事儿。
-
此时灰色集合已经空了,可以进行GC清除了。当年地藏菩萨发宏愿:地狱不空,誓不成佛!如今地狱空了,该成佛了。这是GC收集器将回收白色集合中对象的内存空间,因为它们是绝对不可达的,百分之百的垃圾,可以放心的清除它们。至于对象E,他虽然也是不可达的,但是由于它在黑色区域中,因此此时还不能清除它,需要等到下一轮GC清除时才能将它清除。
-
重置GC,这一轮的GC清除已经完成了,需要为下一轮的GC做好准备。这个准备就是把黑色集合的对象全部再移回白色集合。但实际上并不需要移动,只需要把黑色集合变成白色集合,把白色集合变成黑色集合就行了,也就是把这两个集合的颜色互换一下,不用移动对象。这样一来,对象E就在白色集合中了,并且在下一次GC扫描的时候不会再被移走,将一直残留在白色集合中等着被清除。
以上就是三色标记-清扫算法的全部过程了。