1,4
1-2-3
4-7
5,6
一、go1.3
mark and sweep
1、start stw
2、执行全量扫描并标记
3、清理掉无用的对象
4、stop stw
二、go1.5
开始引入三色标记法
白色(垃圾),灰色(中间状态),黑色(非垃圾)
1、刚开始将全部节点标记为白色
2、开始遍历,只遍历一层(不是递归深度遍历),找到第一层就可达的节点,也就是 1,4,将他们从白色移到灰色。
3、分别遍历1和4,遍历一层,得到2和7,将2和7从白色移到灰色,将1和4从灰色移到黑色。
4、接着遍历灰色表中的节点,也就是2和7,于是2和7被移到了黑色,3被移到灰色。
5、接着遍历灰色表,也就是3,没有子节点,将3移到黑色。
6、遍历完成,最终只剩白色(未遍历)和黑色(已遍历过了)。
在整个过程中,是没有开启STW,那么这必然会导致程序混乱,比如,在完成了步骤5之后,突然程序中出现了4对5的引用,于是需要遵循两个规则。
1、强三色不变式
处于黑色表的对象不允许去引用处于白色表的对象。
2、弱三色不变式
黑色可以引用白色,前提是,同时还有灰色对象直接引用该白色对象,或者可达的链路上游存在灰色对象,即确保此白色对象不被清理。
那么,这两个逻辑要怎么实现呢?于是就有了屏障机制(也就是Hook函数)。
1、为了满足强三色不变式,增加了插入屏障:当对象A引用对象B的时候,对象B需要被标记为灰色。如果对象B在堆上,那么就执行此操作;如果对象B在栈上,那么就不执行此操作,这是为了保证栈的速度,也就意味着对象B还是白色,那么,在清理工作之前还是需要在栈上开启STW扫描一遍,确保B最终也是黑色的,由于栈空间不大,这大概需要10-100毫秒,最后才去执行清理工作。
2、为了满足弱三色不变式,增加了删除屏障:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。这样会导致回收精度低,一个对象即使被删除了,最后一个指向它的指针依然可以活过这一轮,在下轮GC中被清理掉。
三、go1.8
在三色标记法的基础上增加了混合写屏障
上面的插入屏障和删除屏障都或多或少的有点缺点,因此需要进一步优化,引入了混合写屏障。
具体操作
1、GC开始,将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW)。
2、GC期间,任何在栈上创建的新对象,均为黑色。
3、被删除的对象标记为灰色。
4、被添加的对象标记为灰色。添加和创建意思不一样,添加的意思是该对象已经存在,只是添加到某个节点的下方。