内存屏障技术是一种屏障指令,可以让CPU或编译器在执行内存相关的操作时遵循特定的约束,目前多数的现代处理器都会乱序执行指令以最大化性能,但是该技术能够保证内存操作的顺序性,在内存屏障前执行的操作一定会先于内存屏障后执行的操作。想在并发或增量的标记算法中保证正确性,需要达到两种三色不变性的其中一种。
强三色不变性:黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象
弱三色不变性:黑色对象指向的白色对象必须包含一条从灰色对象经由多个白色对象的可达路径
屏障技术分为读屏障和写屏障,但是由于读屏障需要在读操作中加入代码片段,所以对用户程序的性能影响较大。解析一下go语言中使用的两种写屏障技术,插入写屏障和删除写屏障。
每当要执行*slot = ptr时,会先执行写屏障通过shade函数尝试改变指针颜色。如果ptr指针是白色,那么会将该对象设置成灰色。
这张图中可以看到,在一次正常的标记过程中,发生了用户程序修改了指针引用(或者新插入了一个引用关系)的情况,如果我们采用 插入写屏障 我们就需要将新指向的对象标为灰色,以此保证强三色不变性。(对于新指向的对象来说,属于被插入一个引用,所以叫插入写屏障)
在插入写屏障中,我们的标记过程变成(关键第2步):
基本标记流程不变,我们快进到垃圾收集器将根对象指向A对象标记成黑色,并将A对象指向的对象B标记成灰色。
这时用户程序修改A对象的指针,将原本A对象指向B对象的指针指向C对象,这个时候就会触发写屏障将C对象标记为灰色,避免被错误回收。
垃圾收集器依次遍历程序中的其他灰色对象,将他们分别标记成黑色
删除写屏障(YUASA) writePointer(slot, ptr) shade(*slot) *slot = ptr删除写屏障会在老对象的引用被删除的时候,将白色的老对象涂成灰色,这样就可以保证弱三色不变性,老对象引用的下游对象一定可以被灰色对象引用。
使用删除写屏障技术的垃圾收集器和用户程序交替运行的场景中的标记过程:
垃圾收集器将根对象指向A对象标记成黑色并将A对象指向的对象B标记成灰色
如果这时用户程序将B指向C的指针删除,那么C触发删除写屏障,由于C是白色,所以被涂成灰色
垃圾收集器依次遍历程序中的其他灰色对象,将他们分别标记成黑色
第二步触发删除写屏障的着色,因为删除了B指向C的指针,所以C和D分别违反强三色不变性和弱三色不变性,着色后保证了三色不变性,避免悬挂指针。
简单来说就是,在改变指针指向的时候,原来指向的那个对象是白色的话就要变成灰色,以此保证弱三色不变性。(对于老对象来说,引用关系被解除了,所以叫删除写屏障)
增量和并发两种策略优化垃圾收集器不会以为回收垃圾导致长时间STW:
增量垃圾收集:增量得标记和清除垃圾,降低应用程序暂停的最长时间
并发垃圾手机:利用多核的计算资源,在用户程序执行时并发标记和清除垃圾
由于两种方式都需要垃圾收集器与用户程序交替执行,所以需要配合屏障技术。
增量收集器增量收集器将原本时间较长的暂停时间切分成多个更小的GC时间片。增量收集器需要配合三色标记法和屏障技术一起使用。将GC过程分段执行,虽然拉长了总的垃圾回收时间,但是减少了程序STW的时间。不过写屏障还是有些开销的。
并发收集器利用多核优势,将GC过程与用户程序并行执行(大部分情况下),也是要配合屏障技术。
Referenceshttps://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/
https://spin.atomicobject.com/2014/09/03/visualizing-garbage-collection-algorithms/