最开始用stw,标记清除法
-
不定式:
1、强三色不定式 ==>黑不能直连白2、弱三色不定式 ==> 到达一个白色通路上一定要有灰
内存屏障 :
实际上是hook (在基本的赋值操作基础上透明的添加额外的操作)效率考虑,栈不生效。插入写屏障(强三色不定式)
黑连白,白变灰
优点:开始不需要stw全局扫描
缺点:后续需要stw修复栈内问题删除写屏障(弱三色不定式 )
被删除者变灰
缺点:gc开始前stw扫描
优点:后续无需stw
总结关键点:
- 起始赋值器必须打快照,赋值器(根)扫描完成变成黑色,确保前提条件:所有对象都在灰色保护链下
- 插入写屏障对应的是灰色赋值器,删除写屏障对应的是黑色赋值器
为了优化删除写屏障开始需要stw,引入混合写屏障。
举例:如果没有把栈完全扫黑,那么可能出现丢数据,如下:
- A 是 g1 栈的一个对象,g1栈已经扫描完了,并且 C 也是扫黑了的对象;
-
B 是 g2 栈的对象,指向了 C 和 D,g2 完全还没扫描,B 是一个灰色对象,D 是白色对象;
步骤一:g2 进行赋值变更,把 C 指向 D 对象,这个时候黑色的 C 就指向了白色的 D(由于是删除屏障,这里是不会触发hook的)
步骤二:把 B 指向 C 的引用删除,由于是栈对象操作,不会触发删除写屏障;
步骤三:清理,因为 C 已经是黑色对象了,所以不会再扫描,所以 D 就会被错误的清理掉。
解决办法有如下:
方法一:栈上对象也 hook,所有对象赋值(插入,删除)都 hook(这个就不实际了);
所有的插入,删除如果都 hook ,那么一定都不会有问题,虽然本轮精度很差,但是下轮回收可以回收了。但是还是那句话,栈,寄存器的赋值 hook 是不现实的。
方法二:起始快照整栈跨找,扫黑,使得整个堆上的在用对象都处于灰色保护;
整栈扫黑,那么在用的堆上的对象是一定处于灰色堆对象的保护下的,之后配合堆对象删除写屏障就能保证在用对象不丢失。
方法三:加入插入写屏障的逻辑,C 指向 D 的时候,把 D 置灰,这样扫描也没问题。这样就能去掉起始 STW 扫描,从而可以并发,一个一个栈扫描。
- 混合写屏障 = 删除写屏障 + 插入写屏障
混合写屏障:
- 混合写屏障继承了插入写屏障的优点,起始无需 STW 打快照,直接并发扫描垃圾即可;
- 混合写屏障继承了删除写屏障的优点,赋值器是黑色赋值器,扫描过一次就不需要扫描了,这样就消除了插入写屏障时期最后 STW 的重新扫描栈;
- 混合写屏障扫描精度继承了删除写屏障,比插入写屏障更低,随着带来的是 GC 过程全程无 STW;
- 混合写屏障扫描栈虽然没有 STW,但是扫描某一个具体的栈的时候,还是要停止这个 goroutine 赋值器的工作的哈(针对一个 goroutine 栈来说,是暂停扫的,要么全灰,要么全黑哈,原子状态切换);