1.3版本
1.3版本之前使用的是标记清除法:在gc开启时会开启STW,停止程序的运行,从根节点出发,标记所有可达对象,然后停止STW,回收所有未被标记的对象。这个方法的弊端是在整个GC期间需要STW,将整个程序暂停,大大降低了性能。
1.5版本
1.5版本使用的是三色标记法+插入写屏障/删除写屏障,默认是使用插入写屏障
三色标记法的工作原理
在初始时,所有对象都会被标记成白色,gc开始之后,会从根节点开始遍历,将可直达的对象标记为灰色,然后再将灰色可直达的对象标记为灰色,将自己标记为黑色,一直重复这个步骤,直到标记完所有对象,把标记为白色的对象当作垃圾回收掉。
但是对三色标记法来讲,仍然需要依赖STW的. 因为如果不暂停程序, 程序的逻辑改变对象引用关系, 这种动作如果在标记阶段做了修改,会影响标记结果的正确性。
在三色标记法的过程中对象丢失,需要同时满足下面两个条件:
-
条件一:白色对象被黑色对象引用
-
条件二:灰色对象与白色对象之间的可达关系遭到破坏
只要把上面两个条件破坏掉一个,就可以保证对象不丢失,所以就提出了两种破坏条件的方式:强三色不变式和弱三色不变式
强三色不变式
规则:不允许黑色对象引用白色对象
破坏了条件一: 白色对象被黑色对象引用
弱三色不变式
规则:黑色对象可以引用白色对象,但是白色对象的上游必须存在灰色对象
破坏了条件二:灰色对象与白色对象之间的可达关系遭到破坏
插入写屏障:
规则:当一个对象引用另外一个对象时,将另外一个对象标记为灰色。
满足:强三色不变式。不会存在黑色对象引用白色对象
仅会在堆内存中生效,不对栈内存空间生效,这是因为go在并发运行时,大部分的操作都发生在栈上,函数调用会非常频繁。数十万goroutine的栈都进行屏障保护自然会有性能问题
插入写屏障最大的弊端就是,在一次正常的三色标记流程结束后,需要对栈上重新进行一次stw,然后再rescan一次。
删除写屏障
规则:在删除引用时,如果被删除引用的对象自身为灰色或者白色,那么被标记为灰色。
满足弱三色不变式。灰色对象到白色对象的路径不会断
引入删除写屏障,有一个弊端,就是一个对象的引用被删除后,即使没有其他存活的对象引用它,它仍然会活到下一轮。如此一来,会产生很多的冗余扫描成本,且降低了回收精度
对比插入写屏障和删除写屏障
-
插入写屏障:
-
插入写屏障哪里都好,就是栈上的操作管不到,所以最后需要对栈空间进行stw保护,然后rescan保证引用的白色对象存活。
-
-
删除写屏障:
-
在GC开始时,会扫描记录整个栈做快照,从而在删除操作时,可以拦截操作,将白色对象置为灰色对象。
-
回收精度低。
-
1.8版本
1.8版本后使用的是三色标记法+混合写屏障
混合屏障机制的核心定义
-
GC刚开始的时候,会将栈上的可达对象全部标记为黑色。
-
GC期间,任何在栈上新创建的对象,均为黑色。
-
堆上被删除的对象标记为灰色
-
堆上新添加的对象标记为灰色
屏障限制只在堆内存中生效。避免了最后节点对栈进行STW的问题,提升了GC效率
1.9版本
1.9版本的写屏障又进行了进一步优化
在Go1.9之前,很多库函数都是会触发stop-the-world的,而现在只会触发并发的垃圾回收。比如:runtime.GC,debug.SetGCPerent and debug.FreeOSMemory,现在只会触发并发的垃圾回收,只会阻塞当前正在调用的goroutine,直到gc完成。