本文结构速览
Go语言 V1.3之前的标记清除机制
和其他编程语言一样,go语言也有自己的垃圾回收机制。在GO V1.3之前的版本当中,采用的是Mark和Sweep的机制进行垃圾回收。具体来说,在一个程序运行的时候,会创建许多对象,此时在堆空间当中,是可以直接访问到这些对象的。当需要进行垃圾回收的时候,程序首先暂停程序运行所有的服务,然后从根对象开始扫描,直到获得所有可以访问到的对象,剩下那些没有被扫描到的对象,就是我们所需要回收的垃圾。整体过程如下图所示:
- 首先程序触发STW(stop the word),暂停当前所有服务。
- 程序从根对象开始扫描,按照引用顺序扫描所有可达对象,如上图。
- 程序标记所有可达对象,并将不可达到的对象释放掉。
- 恢复程序服务。
在 Go 语言的垃圾回收机制中,根对象指的是那些被程序直接引用的对象,例如全局变量、当前运行函数的参数和局部变量、还有程序栈中的对象等。这些对象被认为是活动对象,也就是说它们可以被访问到,因此不能被垃圾回收器回收。在 Go 中,垃圾回收器会从根对象开始遍历整个对象图,标记所有可达的对象,并进行垃圾回收。如果一个对象不可达,即无法通过根对象访问到,那么它就会被回收。通过这种方式,Go 语言的垃圾回收机制能够自动管理内存,避免了像 C 和 C++ 这样需要手动管理内存的问题。
标记清除法非常简单,但是对程序造成的影响比较大,毕竟暂停程序服务的这段时间,程序是什么也做不了的。
Go语言 V1.5的三色标记法
为了消除STW对程序造成的影响,go语言在1.5版本中引入了三色标记法。具体来说,其基本流程如下图所示:
图1.1 三色标记法启动时
图1.2 第一轮扫描
图1.3 第二轮扫描
图1.4 n轮扫描之后
- 创建对象时所有对象默认为白色。
- 遍历根对象(只遍历一次,遍历邻接对象),得到灰色节点。(初始时所有节点都标记为白色)
- 遍历灰色标记表,得到其可达对象,将其标记为白色,然后将此次灰色表中被遍历之后的节点标记为黑色。
- 重复步骤3,直到灰色标记表中无对象为止。
- 回收白色标记表中的对象。
三色标记法的问题
- 在不使用STW时,如果程序中某个对象在标记为白色的时候,已标记为黑色的对象指向了没有被指向的对象,那么此时这个白色的对象本不应该被回收,却在垃圾回收的时候被回收。
造成这个问题的原因
- 一个白色对象被黑色对象引用。(白色被挂在黑色下)
- 灰色对象与他之间的可达关系的白色对象遭到破坏。(灰色同时丢了该白色对象)
- 1,2同时满足时,就会出现对象丢失现象。
强三色不变式和弱三色不变式
强三色不变式
强制性不允许黑色对象引用白色对象。
弱三色不变式
黑色对象可以引用白色对象,白色对象存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象。
屏障机制
插入写屏障
对象被引用时触发的机制。具体操作:在A对象引用B对象的时候,B被标记为灰色。(将B挂在A的下游,B必须被标记为灰色)。这种机制满足了强三色不变式。
// 伪码
添加下游对象(当前下游对象slot,新下游对象ptr){
// 1
标记灰色(新下游对象ptr)
// 2
当前下游对象slot = 新下游对象ptr
}
- 场景1.A添加下游对象(nil,B)// A之前没有下游,新添加一个下游对象B,B标记为灰色
- 场景2.A添加下游对象(C,B) // A将下游对象C换成B,B被标记为灰色
不足
无法处理栈空间内的黑色对象已引用白色对象,需要在结束时使用STW重新扫描栈区。
删除写屏障
对象被删除前触发的机制。具体操作:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。这种机制满足了弱三色不变式。
// 伪码
添加下游对象(当前下游对象slot,新下游对象ptr){
// 1
if(当前下游对象slot是灰色 || 当前下游对象slot是白色){
标记灰色(当前下游对象) // slot为被删除的对象,标记为灰色
}
// 2
当前下游对象slot = 新下游对象ptr
}
- 场景1.A添加下游对象(B,nil)// A之前没有下游,新添加一个下游对象B,B标记为灰色
- 场景2.A添加下游对象(B,C) // A将下游对象C换成B,B被标记为灰色
不足
回收精度低
go语言混合写屏障机制
具体操作:
- GC开始时将栈上的对象全部扫描并标记为黑色。
- GC期间,任何在栈上创建的新的对象,均为黑色。
- 被删除的对象标记为灰色。
- 被添加的对象标记为灰色。 满足:变形的弱三色不变式(结合了插入、删除写屏障两者的优点)
// 伪码
添加下游对象(当前下游对象slot,新下游对象ptr){
// 1
标记灰色(当前下游对象slot)// 只要当前下游对象被移走,就标记为灰色
// 2
标记灰色(新下游对象ptr)
// 3
当前下游对象slot = 新下游对象ptr
}