Golang 的垃圾回收

159 阅读5分钟

Go V1.3之前的标记清除

流程

  1. 暂停程序业务逻辑,找出不可达对象和可达对象
  2. 开始标记,找出程序所有可达对象,并做上标记
  3. 标记完成后,开始清除未标记对象
  4. 停止暂停,让程序继续跑,然后循环这个过程,直至process生命周期结束

缺点

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

Go V1.5 三色标记法

image.png

  1. 新创建的对象都标记为白色

流程图 (3).jpg

  1. 每次GC回收开始,从根节点遍历所有对象,把遍历到的对象从白色标记表,放入色标记表

流程图 (4).jpg 3. 遍历灰色标记表,将灰色对象引用的对象从白色结合放入到灰色标记表,然后将该灰色对象放入黑色标记表

流程图 (5).jpg 4. 重复第三步,直至灰色标记表中无任何对象

流程图 (6).jpg

  1. 回收所有的白色标记表的对象,也就是回收垃圾

对象丢失的问题

三色标记法如果启动STW同样会导致程序卡顿问题。

如但,如果三色标记过程不启动STW,就会存在对象丢失问题。解释如下:

流程图 (7).jpg

现在垃圾回收进展到如上图所示的状态,因为没有启动STW,所有对象是并行向下执行,在某一时刻可以同时存在如下动作:

  • 对象2解除对对象3的引用
  • 对象4引用对象3

流程图 (8).jpg

GC继续扫描灰色对象,新一轮的对象标记就会变成下图这样。对象2对象7标记成黑色。此时,不存在灰色对象,接下来回收白色对象。对象3就会被回收,但是此时的对象3是被对象4引用的,不应该被回收,于是就发生了对象丢失。

流程图 (9).jpg

三色标记最不希望发生的事

image.png

上述两个事件同时发生,就会导致对象丢失。最简单的方法就是使用STW。如何能在保证对象不去失的情况尽可能的提局GC效率,减少STW时间呢?

强弱三色不变式

如果三色标记满足强弱不变式之一,就可以保证不丢失对象

强三色不变式

强制禁止黑色对象引用白色对象

流程图 (10).jpg

弱三色不变式

黑色对象可以引用白色对象,但白色对象必须被灰色对象直接或间接引用。

弱三色不变式就是确保不能存在白色的联通分量。(就是白色的不能单独自己一堆)

流程图 (11).jpg

屏障机制

插入屏障

  • 触发时机:插入对象时。在B对象被A对象引用时,B强制被标记为灰色。
  • 满足: 满足强三色不变式,不存在黑色对象引用白色对象的情况了,因为白色对象被引用时会被强制变成灰色。

插入屏障流程

流程图 (12).jpg

由于并发特性,可同时发生如下三个动作

  • 对象4引用对象8
  • 对象1引用对象9

优于栈空间是不触发插入屏障的。因此,对象1不会触发插入屏障,只有对象4会触发插入屏障。

流程图 (13).jpg

继续执行标记,直至没有灰色对象。

流程图 (14).jpg

这个时候,开始清扫垃圾就会导致对象9被删除。于是,在清扫垃圾前会把栈上的节点全部置白,此时添加STW保护栈,防止外界的干扰。然后对栈中的元素继续标记。

流程图 (15).jpg

插入屏障总结

  • 不足: 结束时需要STW来重新扫描栈,大约需要10~100ms

  • 场景:

    • A.添加下游对象(C, B)
流程图 (16).jpg
  • A.添加下游对象(nil, B)
流程图 (17).jpg

删除屏障

删除屏障其实也可以理解为插入,就是插入个空对象。

  • 触发时机:删除对象时。被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。

对象被删除时,置为灰色,保留一轮。即便是没有新的对象引用它,在下一轮扫描时也会被删除。

流程图 (18).jpg
  • 满足: 满足弱三色不变式,保护灰色对象到白色对象的路径不会断

删除屏障流程

现在有初始状态如下,需要删除对象1对象5的引用。

流程图 (19).jpg

删除红色引用后,如下图

流程图 (20).jpg

继续循环标记,直到没有灰色节点

流程图 (21).jpg

删除屏障总结

  • 不足: 回收精度低。一个对象即使被删除了,仍然能活过这一轮,等下一轮被回收。

Go V1.8的三色标记法+混合写屏障机制

具体操作

  1. GC开始将栈上的对象全部扫描标记为黑色
  2. GC期间,任何栈上创建的对象均为黑色
  3. 被删除的对象标记为灰色
  4. 被添加的对象标记为灰色
  5. 栈对象不启动屏障

场景一

对象被堆对象解除引用,成为栈对象的下游

  • 对象4解除对对象7的引用
  • 对象1引用对象7

流程图 (22).jpg

对象4是栈对象,解除对对象7的引用时将对象7置灰。

对象1是栈对象,引用对象7时不启用屏障,直接挂在下边

流程图 (23).jpg

场景二

对象被一个栈对象解除引用,成为另一个对象的下游

  • 在栈上新建一个对象9
  • 对象9引用对象3
  • 对象2解除对对象3的引用

流程图 (24).jpg

栈上新建的对象均标记为黑色,因此对象9为黑色。

无论是对象9引用对象3,还是对象2解除对对象3的引用,由于对象9和对象2均是栈对象,不启用屏障,因此对象3不会有变化。

流程图 (25).jpg

总结

  • Go V1.3晋通的标记清除法,整体过程需要STW、效率极低
  • Go V1.5三色标记法,堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW、效率普通
  • Go V1.8 三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动, 整体过程⼏几乎不不需要STW, 效率较高