上文《Golang垃圾回收简明教程2--三色标记法》中已经阐明,强弱三色不变式可以破环由于不开启STW情况下三色标记导致对象丢失的两个条件。而强弱三色不变式可以通过屏障机制实现。本文主要来探讨什么是屏障机制。

屏障机制有两种,分别是插入屏障和删除屏障。插入屏障,是对象被引用时触发的机制;删除屏障,是对象被删除时触发的机制。
下面分别详细介绍插入屏障与删除屏障。
插入写屏障
插入屏障拦截将白色指针插入黑色对象的操作,标记其对应对象为灰色状态。这样就不存在黑色对象引用白色对象的情况,满足了强三色不变式。
新下游对象ptr为了更好理解上面的伪代码设定以下场景:
首先需要假设的一点是A对象已经被标记为黑色。场景1是A之前没有下游对象,新添加一个下游对象B,B被标记为灰色;场景2是A将下游对象C更换为对象B,B被标记为灰色。
插入屏障是一个很耗费性能的行为,而栈需要更高的性能要求,因此,插入屏障技术只运用在堆内存空间里,不会运用到栈里。
借助之前文章
的图下面简单介绍插入屏障的流程:

在原来的基础上内存块划分了栈和堆内存,经过几次的标记后,栈内存中的对象和堆内存中的对象4已经被标记为黑色。对象1添加新引用对象9,由于在栈中没有启用写屏障,所以对象9依然被标记为白色;对象4添加新引用对象8,由于在堆中启动了写屏障,所以对象9会被标记为灰色。
正常继续采用三色标记法,最终结果可用得到对象7和对象9会被清理,其余对象得到保留。这种结果显然不是我们想要的,因为栈中的对象9被对象1所引用,不能被清理。

其实,GC在准备清理白色标记的对象前,会重新遍历扫描一次栈空间。这时需要加STW保护栈。具体的操作是:在GC清理前,把栈对象全部标记为白色。如下图:

然后对栈对象加STW保护,不允许在栈中创建或删除对象:

最终可以得到预期的结果了:

最后可以正常清理标记为白色的对象了。
总结:GC中添加了写屏障机制可以极大地减少了STW的时间,只是需要在结束时启动STW来重新扫描栈。Golang在使用三色标记法之前是采用标记-清除法来清理内存的,其需要在堆和栈同时添加STW保护,而使用了三色标记法和写屏障机制后,可以在回收堆对象时不使用STW,只是在栈中使用,但栈的对象数目少,STW的时间也不会太长。
删除写屏障
删除屏障也是拦截写操作,因为它是写入一个空对象。具体的操作是,被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。满足了弱三色不变式原则,保护灰色对象到白色对象的可达路径不会断。
其伪代码如下:
为了更好理解上面的伪代码设定以下场景:
场景1,A对象删除B对象的引用,那么B对象被A对象删除,B对象被标记为灰色;场景2,A对象更换下游对象由B变成C,B对象被A对象删除,B对象被标记为灰色。
下面简单介绍删除写屏障的流程:
程序创建之初,所有对象都被标记为白色。经过遍历RootSet后,对象1和对象4被标记为灰色。

当程序运行时,对象1准备删除对象5。

此时触发删除写屏障,被删除的对象5被标记为灰色。

一轮GC过后得到最终结果。

看到最终结果可能不是大家所希望的,对象5已经被对象1删除了,为啥GC不把它清理呢?其实这样做是为了满足弱三色不变式原则,被删除对象都需要标记为灰色,假如对象4此时添加了对象5的引用,那么对象5就会被无辜删除了。那么对象5什么时候被删除呢?答案是等到下一轮GC,对象1肯定到达不了对象5,同时也没有从根节点出发的对象到达对象5,那么对象5就会被合法地删除。
我们可以看到删除写屏障的不足:回收精度低,一个对象即使被删除了最后一个指向它的指针,也依然可以活一轮GC,在下一轮GC中才被清除。
早安~