标记-清除(mark and sweep)算法
这个是Go 1.3之前使用的垃圾回收算法。
我们可以看下这个算法的流程:
- 暂停程序业务逻辑, 从根节点开始遍历内存对象,分类出可达和不可达的对象,然后做上标记。
- 开始标记,程序找出它所有可达的对象,并做上标记。
- 标记完了之后,然后开始清除未标记的对象。
- 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。
操作非常简单,但是有一点,该算法在执行的时候,需要程序暂停!即 STW(stop the world),STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程的影响很大,所以STW也是一些回收机制最大的难题和希望优化的点。所以在执行第三步的这段时间,程序会暂定停止任何工作,卡在那等待回收执行完毕。
三色并发标记法
Go语言从版本1.5开始引入了三色并发标记法,这是一种用于垃圾回收的技术。三色并发标记法的目标是尽可能减少垃圾回收对程序的停顿时间。下面是三色并发标记法的详细步骤:
我们看下之前的三色并发标记法,假设有三个颜色的桶,白色,灰色,黑色。
- 所有的对象一开创建的时候都是放在白桶里。
- 从程序的根节点对象开始遍历根节点所引用的所有对象,遍历到的对象放进灰桶里面
- 遍历灰桶里面的所有对象,遍历到的新对象放进灰桶里面,同时将当前对象丢进黑桶里
- 重复3,一直到所有的对象都遍历完了。
- 这时候只有白桶和黑桶里有对象了,清除白桶里面剩余的对象。
关键问题就是步骤3,如果遍历步骤3的时候不启动STW暂停程序,会有并发问题。
假设第N次遍历,此时A对象是灰色,B对象是黑色。A引用了C对象,此时C还是白色的。
由于程序是没有暂停的,程序执行了操作,A不再引用C对象,但是B又引用了C对象。
那么到了第N+1次遍历,就会出现这样的情况,B引用了C,由于A不再引用C,C将永远留在白桶里,C最终要被清除掉。问题出现了,B引用了C,但是C还是被清除了。
强三色不变性
— 黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象;弱三色不变性
— 黑色对象指向的白色对象必须包含一条从灰色对象经由多个白色对象的可达路径
遵循上述两个不变性中的任意一个,我们都能保证垃圾收集算法的正确性。
单独使用插入屏障
原理就是上面的强三色不变性,要解决这个问题可以引入插入屏障
,插入屏障意思就是黑桶里面的对象引用白桶里面的对象,那么这个对象将被强制放进灰桶。缺点:
结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;
单独使用删除屏障
原理就是上面的弱三色不变性,就是A不再引用C时,C强制放到进灰桶。缺点:
这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉。
栈的特殊性
插入屏障和删除屏障都不会在栈上使用,go是并发运行的,大部分的操作都发生在栈上,数十万goroutine的栈都进行屏障保护会有非常大的性能开销。
插入屏障中,会标记栈中的颜色,但是栈中对象引用新对象不会更改引用对象的颜色。这样当全部三色标记扫描之后,栈上有可能依然存在白色对象被引用的情况,所以需要STW,重新扫描一次栈上的对象,这个就是Go1.5采用的方案。
删除屏障中,
各个版本的方案
Go1.3
普通的标记清除法, 整体过程需要STW,效率极低
Go1.5
三⾊标记法,堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描⼀次栈(需要STW), 效率普通.
Go1.8
三⾊标记法,混合写屏障机制,栈空间不启动,堆空间启动,整体过程⼏乎不需要STW,效率较⾼
- GC开始将栈上的对象全部扫描并标记为⿊⾊(之后不再进⾏第⼆次重复扫描,⽆需STW)
- GC期间,任何在栈上创建的新对象,均为⿊⾊。
- 被删除的对象标记为灰⾊。
- 被添加的对象标记为灰⾊。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。