Go V1.3之前的标记清除
流程
- 暂停程序业务逻辑,找出不可达对象和可达对象
- 开始标记,找出程序所有可达对象,并做上标记
- 标记完成后,开始清除未标记对象
- 停止暂停,让程序继续跑,然后循环这个过程,直至process生命周期结束
缺点
- STW:stop the world;让程序暂停,程序出现卡顿
- 标记需要扫描整个heap
- 清除数据会产生heap碎片
Go V1.5 三色标记法
- 新创建的对象都标记为白色
- 每次GC回收开始,从根节点遍历所有对象,把遍历到的对象从白色标记表,放入色标记表
3. 遍历灰色标记表,将灰色对象引用的对象从白色结合放入到灰色标记表,然后将该灰色对象放入黑色标记表
4. 重复第三步,直至灰色标记表中无任何对象
- 回收所有的白色标记表的对象,也就是回收垃圾
对象丢失的问题
三色标记法如果启动STW同样会导致程序卡顿问题。
如但,如果三色标记过程不启动STW,就会存在对象丢失问题。解释如下:
现在垃圾回收进展到如上图所示的状态,因为没有启动STW,所有对象是并行向下执行,在某一时刻可以同时存在如下动作:
对象2解除对对象3的引用对象4引用对象3
GC继续扫描灰色对象,新一轮的对象标记就会变成下图这样。对象2,对象7标记成黑色。此时,不存在灰色对象,接下来回收白色对象。对象3就会被回收,但是此时的对象3是被对象4引用的,不应该被回收,于是就发生了对象丢失。
三色标记最不希望发生的事
上述两个事件同时发生,就会导致对象丢失。最简单的方法就是使用STW。如何能在保证对象不去失的情况尽可能的提局GC效率,减少STW时间呢?
强弱三色不变式
如果三色标记满足强弱不变式之一,就可以保证不丢失对象
强三色不变式
强制禁止黑色对象引用白色对象
弱三色不变式
黑色对象可以引用白色对象,但白色对象必须被灰色对象直接或间接引用。
弱三色不变式就是确保不能存在白色的联通分量。(就是白色的不能单独自己一堆)
屏障机制
插入屏障
- 触发时机:插入对象时。在B对象被A对象引用时,B强制被标记为灰色。
- 满足: 满足强三色不变式,不存在黑色对象引用白色对象的情况了,因为白色对象被引用时会被强制变成灰色。
插入屏障流程
由于并发特性,可同时发生如下三个动作
对象4引用对象8对象1引用对象9
优于栈空间是不触发插入屏障的。因此,对象1不会触发插入屏障,只有对象4会触发插入屏障。
继续执行标记,直至没有灰色对象。
这个时候,开始清扫垃圾就会导致对象9被删除。于是,在清扫垃圾前会把栈上的节点全部置白,此时添加STW保护栈,防止外界的干扰。然后对栈中的元素继续标记。
插入屏障总结
-
不足: 结束时需要STW来重新扫描栈,大约需要10~100ms
-
场景:
A.添加下游对象(C, B)
A.添加下游对象(nil, B)
删除屏障
删除屏障其实也可以理解为插入,就是插入个空对象。
- 触发时机:删除对象时。被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。
对象被删除时,置为灰色,保留一轮。即便是没有新的对象引用它,在下一轮扫描时也会被删除。
- 满足: 满足弱三色不变式,保护灰色对象到白色对象的路径不会断
删除屏障流程
现在有初始状态如下,需要删除对象1对对象5的引用。
删除红色引用后,如下图
继续循环标记,直到没有灰色节点
删除屏障总结
- 不足: 回收精度低。一个对象即使被删除了,仍然能活过这一轮,等下一轮被回收。
Go V1.8的三色标记法+混合写屏障机制
具体操作
- GC开始将栈上的对象全部扫描标记为黑色
- GC期间,任何栈上创建的对象均为黑色
- 被删除的对象标记为灰色
- 被添加的对象标记为灰色
- 栈对象不启动屏障
场景一
对象被堆对象解除引用,成为栈对象的下游
对象4解除对对象7的引用对象1引用对象7
对象4是栈对象,解除对对象7的引用时将对象7置灰。
对象1是栈对象,引用对象7时不启用屏障,直接挂在下边
场景二
对象被一个栈对象解除引用,成为另一个对象的下游
- 在栈上新建一个
对象9 对象9引用对象3对象2解除对对象3的引用
栈上新建的对象均标记为黑色,因此对象9为黑色。
无论是对象9引用对象3,还是对象2解除对对象3的引用,由于对象9和对象2均是栈对象,不启用屏障,因此对象3不会有变化。
总结
- Go V1.3晋通的标记清除法,整体过程需要STW、效率极低
- Go V1.5三色标记法,堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW、效率普通
- Go V1.8 三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动, 整体过程⼏几乎不不需要STW, 效率较高