标记清除法
该方法是golang v1.3的垃圾回收机制
流程
假设一个程序和对象的可达关系如下:
则具体回收机制如下:
- 首先执行STW(Stop the World)
- 遍历所有对象并将全部可达对象标记(图中为1 2 3 4 7)
- 没有被标记的即为垃圾,会被回收
- 结束STW
缺点
- STW会让程序暂停,程序出现卡顿
- 标记时需要遍历整个heap
- 清除数据会产生heap碎片
早期有尝试过将STW和清除交换顺序,使STW窗口变小,但是用处不大
三色标记法
流程
三色标记法会统计三个集合:白色、灰色和黑色
程序创建时,将所有节点标记为白色,放入白色集合
将程序展开为根集合,以根集合中的节点为起点,遍历根集合(非递归),得到灰色节点,并移入灰色集合
接下来遍历灰色集合,将可达对象从白色标记为灰色,遍历后的灰色标记为黑色,移入黑色集合
重复上一步,直到灰色集合中没有任何对象,此时剩余的白色对象就是垃圾,收集所有白色对象
无STW
若没有STW:一开始对象2的指针指向对象3,在还没扫描到对象2的时候,已被标记为黑色的对象4创建了指向对象3的指针,与此同时对象2移除指向对象3的指针,此时对象3就挂在了已经扫描完成的黑色对象4下
按照正常逻辑,对象2 3标记为黑色,而对象3无法被遍历到,会被认为是垃圾,等待回收清除
综上,在不STW的情况下,如果同时满足如下两个条件,就会出现对象丢失的现象:
- 一个白色对象被黑色对象引用
- 灰色对象和它之间的可达关系的白色对象遭到破坏
强弱三色不变式
通过两种三色不变式来破坏上面的两种条件
强三色不变式:强制性的不允许黑色对象引用白色对象
弱三色不变式:白色对象存在其他灰色对象对他的引用,或者可达它的链路上游存在灰色对象时,黑色对象可以引用白色对象
在三色标记中如果满足强/弱三色不变式之一,即可保证对象不丢失
屏障
屏障提供了一种额外的判断机制,不打扰正常程序运行
插入屏障
在A对象引用B对象时,B对象被标记为灰色(即将B挂在A下游时,B必须被标记为灰色),它满足强三色不变式
注意:插入屏障不在栈上执行,为了不拖累栈的效率
为了防止漏扫,在回收白色之前,加STW重新扫描一次栈空间,进行一次三色标记
插入写屏障因为要加一次STW,所以会慢10-100ms
删除屏障
被删除的对象如果自身为灰色或者白色,则标记为灰色,它满足弱三色不等式
假如这里对象1要删除对象5,对象5是白色的,则自身被标记为灰色
它的不足之处在于回收精度低:一个对象即使被删除了最后一个指向它的指针也可以活过这一轮,在下一轮GC被清理掉
混合写屏障
操作如下:
- GC开始时将栈上的对象全部扫描并标记为黑色
- GC期间,任何在栈上创建的新对象,均为黑色
- 被删除、添加的对象标记为灰色
它满足变形的弱三色表达式
注意·:为了保证效率,栈不启用屏障
GC开始时,默认为白色,优先扫描栈,把栈中的可达对象全部标记为黑色
场景1
对象被一个堆对象删除引用,成为栈对象的下游
如:对象7添加到对象1的下游,对象4删除对象7的引用,此时对象4处于堆区,会触发写屏障,标记对象7为灰色
场景2
对象被一个栈对象删除引用,称为另一个栈对象的下游
如:我们新建一个对象9在栈上,此时会被标记为黑色,对象9添加下游对象3,然后对象2删除对象3的引用关系
若没有new对象9,直接删除2和3的关系,则会触发弱三色不变式,延迟一轮删除对象3
场景3
对象被一个堆对象删除引用,成为另一个堆对象的下游
如:堆对象10已经被扫描为黑,它添加下游引用堆对象7,触发屏障机制,标记为灰色
场景4
对象从一个栈对象删除引用,成为另一个堆对象的下游
如:接下来栈对象1要删除栈对象2的引用,堆对象4删除引用对象7的关系,转移到对象2,此时触发写屏障,对象7被标记为灰色