参考:https://juejin.cn/post/7040737998014513183#comment
垃圾回收(Garbage Collection,缩写为GC),是一种自动内存管理机制。
相关术语
赋值器:说白了就是你写的程序代码,在程序的执行过程中,可能会改变对象的引用关系,或者创建新的引用。
回收器:垃圾回收器的责任就是去干掉那些程序中不再被引用得对象
STW:全称是stop the word,GC期间某个阶段会停止所有的赋值器,中断你的程序逻辑,以确定引用关系。
root对象:根对象是指赋值器不需要通过其他对象就可以直接访问到的对象,通过Root对象, 可以追踪到其他存活的对象。常见的root对象有:栈变量、堆变量。
GC发展过程
v1.3 标记清除法
GC过程开启STW,从root根节点出发标记所有可达节点,停止STW后回收未标记的节点。
弊端:在整个GC期间需要STW,将整个程序暂停,有极大的性能损耗。
v1.5 三色标记法
对象分为黑、白、灰三种颜色。最终,可达对象都会变成黑色,不可达对象为白色,灰色为中间过渡态。
最后回收不可达的白色对象,GC操作完成。
在这个过程中仍然需要进行STW,因为在每次GC操作过程中,只有灰色对象下游的白色对象会被扫描(判断是否需要回收)。如果在GC过程中由于程序的运行出现黑色对象引用白色对象的情况,那么存活的白色对象会被GC程序误杀。
图:原先2->3,经过程序运行,变成6->3。
Golang是如何解决这个STW问题的呢?
其实总结来看,在三色标记法的过程中对象丢失,需要同时满足下面两个条件:
条件一:白色对象被黑色对象引用
条件二:灰色对象与白色对象之间的可达关系遭到破坏
看来只要把上面两个条件破坏掉一个,就可以保证对象不丢失,所以我们的golang团队就提出了两种破坏条件的方式:强三色不变式和弱三色不变式。
强三色不变式
规则:不允许黑色对象引用白色对象
破坏了条件一: 白色对象被黑色对象引用
解释:如果一个黑色对象不直接引用白色对象,那么就不会出现白色对象扫描不到,从而被当做垃圾回收掉的尴尬。
弱三色不变式
规则:黑色对象可以引用白色对象,但是白色对象的上游必须存在灰色对象
破坏了条件二:灰色对象与白色对象之间的可达关系遭到破坏
解释: 如果一个白色对象的上游有灰色对象,则这个白色对象一定可以扫描到,从而不被回收
屏障机制
Golang团队遵循上述两种不变式提到的原则,分别提出了两种实现机制:插入写屏障和删除写屏障。
插入写屏障:仅在堆上生效
规则:当一个对象引用另外一个对象时,将另外一个对象标记为灰色。
满足:强三色不变式。不会存在黑色对象引用白色对象
插入屏障仅会在堆内存中生效,不对栈内存空间生效,因为栈空间操作过于频繁,会引发性能问题。
弊端:在一次正常的三色标记流程结束后,由于插入写屏障不会对栈空间起作用,仍有可能在栈空间出现黑色对象引用白色对象的情况,需要对栈上重新进行一次stw,然后再rescan一次。
删除写屏障
规则:在删除引用时,如果被删除引用的对象自身为灰色或者白色,那么被标记为灰色。
满足弱三色不变式。灰色对象到白色对象的路径不会断
解释:白色对象始终会被灰色对象保护
弊端:就是一个对象的引用被删除后,即使没有其他存活的对象引用它,它仍然会活到下一轮。如此一来,会产生很多的冗余扫描成本,且降低了回收精度。
对比插入写屏障和删除写屏障:
插入写屏障:
插入写屏障哪里都好,就是栈上的操作管不到,所以最后需要对栈空间进行stw保护,然后rescan保证引用的白色对象存活。
删除写屏障:
在GC开始时,会扫描记录整个栈做快照,从而在删除操作时,可以拦截操作,将白色对象置为灰色对象。
回收精度低。
v1.8 混合写屏障机制
混合屏障机制的核心定义:
GC刚开始的时候,会将栈上的可达对象全部标记为黑色。
GC期间,任何在栈上新创建的对象,均为黑色。
上面两点只有一个目的,将栈上的可达对象全部标黑,最后无需对栈进行STW,就可以保证栈上的对象不会丢失。有人说,一直是黑色的对象,那么不就永远清除不掉了么,这里强调一下,标记为黑色的是可达对象,不可达的对象一直会是白色,直到最后被回收。
堆上被删除的对象标记为灰色
堆上新添加的对象标记为灰色
总结
Golang v1.3之前采用传统采取标记-清除法,需要STW,暂停整个程序的运行。
在v1.5版本中,引入了三色标记法和插入写屏障机制,其中插入写屏障机制只在堆内存中生效。但在标记过程中,最后需要对栈进行STW。
在v1.8版本中结合删除写屏障机制,推出了混合屏障机制,屏障限制只在堆内存中生效。避免了最后节点对栈进行STW的问题,提升了GC效率