一、写屏障技术

在前面的写屏障分析中,提到了一些如三色增量垃圾回收算法的相关技术,后来觉得还是有必要把这些技术分析一下,以便更清晰明白的懂得垃圾回收中对一些技术的应用方式和历史延革。写屏障技术其实就是一种内存的控制技术,如果有过开发多线程并发的经验就很容易理解这些内容。
在内存的管理中,处理内存是否被多个应用对象(包括线程、进程或者其它类对象等)操作是一个很重要的问题。除了防止出现数据不一致的现象,更重要的是,防止内存对象的被误删除。一般来说,内存对象有少量的留在,也就是常说的内存泄露,这并不是一个多么致命的问题。只要其能保证不持续的增长就可以。但内存对象一旦被误删除,那么对应用程序是非常可怕,现象就是直接崩溃。
同样,在GC中,也会有这种情况,一次无法回收干净的垃圾内存对象,可以等待下一次回收,但是如果回收了正在别人使用的内存对象,则对应用程序来说是致命的。那么很多人在研究这个控制技术,在三色标记算法中,比较典型的有两类技术,一种是Dijkstra 另外一种是Yuasa。下面就这两个具体分析一下。
需要说明的是,相关的技术还有不少,有兴趣的可以去网上查找分析对比一下。

二、Dijkstra 和Yuasa 技术

1、Dijkstra
Dijkstra 写屏障处理如下:
writePointer(slot, ptr):
shade(ptr)
slot = ptr
shade(ptr)ptr如果对象不是灰色或黑色,则将其标记为灰色。这通过保守地假设
slot可能在黑色物体中来确保强三色不变性,并确保ptr在加载之前不能是白色的*slot。
Dijkstra 屏障与其他类型的屏障相比具有几个优点。由于不需要对指针读取进行任何特殊处理,所以它具有较强的性能优势。它还能保证指针的安全递进。
它的缺点是需要STW对堆栈进行二次扫描。在具有大量活动 goroutine 的应用程序中,重新扫描堆栈可能需要 10 到 100 毫秒。
如下图:
在这里插入图片描述

2、Yuasa
Yuasa 在 1990 年的论文 Real-time garbage collection on general-purpose machines 中提出了删除写屏障,因为一旦该写屏障开始工作,它会保证开启写屏障时堆上所有对象的可达,所以也被称作快照垃圾收集(Snapshot GC),该算法会使用如下所示的写屏障保证增量或者并发执行垃圾收集时程序的正确性:
writePointer(slot, ptr)
shade(*slot)
*slot = ptr
上述代码会在老对象的引用被删除时,将白色的老对象涂成灰色,这样删除写屏障就可以保证弱三色不变性,老对象引用的下游对象一定可以被灰色对象引用。
如下图:

在这里插入图片描述

三、Golang的混合写屏障技术

混合写入屏障结合了 Yuasa 风格的删除写入屏障 和 Dijkstra 风格的插入写入屏障 。混合写屏障实现如下:
writePointer(slot, ptr):
shade(*slot)
if current stack is grey:
shade(ptr)
*slot = ptr
即写屏障会遮蔽其引用被覆盖的对象,并且如果当前 goroutine 的堆栈尚未被扫描,也会遮蔽正在使用的引用。
混合屏障降低了对堆栈重新扫描的要求,堆栈内的对象会保持黑色,它基本不需要对堆栈的重新扫描和屏障机制并严格限制的 stop-the-world 时间。混合屏障不需要读屏障,因此指针读取是常规内存读取;这确保的对象引用的单调递进。
混合屏障的缺点是可能会导致更多的浮动垃圾,因为它会在标记阶段的任何时候保留从根(堆栈除外)可到达的所有内容。混合屏障还禁止某些优化:特别是如果 Go 编译器在处理静态空指针时,无法优化对写屏障的省略。
混合写入屏障初步实验表明,这可以将最坏情况的 STW 时间减少到 50µs 以下,并且这种方法可以使完全消除 STW 标记终止变得切实可行。

四、总结

其实写屏障的方法或者替代写屏障的方法还有不少,Golang是从这些方法中,认为这种方式最好(当然指的是在go1.8,后面的版本又继续优化了)。正如反复强调所说,在没有革命性的技术出现前,都是在前人的基础上不断进行革新。又或者把多个技术组合形成一种新的工程技术。
人们总说卷,其实,卷恰恰是从技术开始的。当天才的人类无法出现时,卷是早晚的事儿。社会上的卷只不过是技术传导到了过去而已。