golang 垃圾回收-三色标记法(白话版)

对于golang 垃圾回收的了解,我理解更多的就是了解,实际做项目能用到垃圾回收的知识点不多,但有些晦涩难懂的语言,是我们的绊脚石,对于技术怎么能理解就怎么记忆。

1. golang垃圾回收的基础:标记(Mark)、清扫(Sweep)

1)首先把我们的对象简单归个类,有些对象包含基础类型,这类对象回收比较省心,只要该对象没人调用,那就可以回收了。

type aa struct {
	A int
	B int
}

2)还有对象中包含指针,指针指向另一个对象,这种对象回收 不能只考虑本对象,还要考虑指向的对象,如果父对象都没有回收,却把指针指向的对象删除,那妥妥的内存泄漏。

type aa struct {
	A *bb
	B int
}

type bb struct {
	X int
	Y int
}

3)假设应用代码片段如下(不要较真这段程序的对象在栈上分配,示意):

	a := aa{}
	b1 := &bb{
		X: 1,
		Y: 2,
	}
	b2 := &bb{
		X: 3,
		Y: 4,
	}
	a.B = b1
	a.B = b2

系统有三个对象分别为a、b1、b2
![在这里插入图片描述](https://img-blog.csdnimg.cn/80abf3e538ce4858a9fefc8cee713f30.png
4)开始标记根对象,假设跟对象为a为黑色、由于a与b2是父子对象,也标记为黑色。
在这里插入图片描述
5)最后发现b1 是白色的,执行清扫流程,然后就回收了。

问题:可以看出标记、清扫逻辑清晰,实现简单,真实GC界的翘楚! 但是,这里有个严重的问题,如果刚扫描完成b2标记为黑色,执行a.B = b1代码,a又指向了b1,但b1没有被扫描,非常幸运b1被回收了。。。

为了解决这个问题,很简单从源头解决,不让a.B = b1 代码执行就行了,也就是STW。但对标记整个流程STW,性能自然是不高的。

2. 三色标记法
1)三色标记法介绍

对上述标记、清扫流程改进,三色标记法。

白色:没有被标记的对象
灰色:被扫描到的对象
黑色:所有子对象都被扫描后,由灰色变成黑色。

还是上面的例子:

  • 扫描到a的样子,a变成灰色。
    在这里插入图片描述
  • 扫描到b2的样子,b2 变成灰色
    在这里插入图片描述
  • 扫描完成后的样子,a、b2 都变成黑色
    在这里插入图片描述
  • 执行清扫流程,将白色的对象回收。

这效果非常棒,成功实现对象回收!!但是… 这和上面的标记、清扫没啥区别啊,STW问题一个没解决,还换了好几个颜色,崩溃啊!!

2)三色不变性

仔细思考,一个对象被错误回收需要两个必要条件,也就是只要满足下面条件,就会发生内存泄露。
1)强三色不变性:如果出现一个黑色对象指向白色对象。
2)弱三色不变性:白色对象没有一条灰色对象指向他。这句话有点绕,场景:如果一个白色的对象,没有任何一条灰色的对象指向他,则满足第二个条件。

我们上面的案例分析,满足两个条件,黑色指向白色,且没有灰色指向白色,出现内存泄露。
在这里插入图片描述
那么只要我们想办法破坏这两个不变性中的一个,就不会出现内存泄露。

3)写屏障

屏障技术网上有很多,这里我们抽取和我们有关的描述,可以在内存操作前执行特定的逻辑。注意:内存在golang程序中分为堆、栈,golang是对堆使用屏障技术,可能是技术实现难度大,毕竟每次栈操作调用其他函数处理、还要保证性能。

插入写屏障

目标:打破强三色不变性,也就是不让对象标记期间,出现黑色指向白色的情况。当有白色对象挂到黑色对象下面时,将白色变成灰色。

还是按照上面的案例,当我们开始垃圾回收的时候,不进行STW,直接开启写屏障,当执行a.B = b1命令时触发写屏障逻辑,将b1 标记为灰色。最终如下图所示:
在这里插入图片描述

有了插入写屏障,打破强三色不变性,对象不会错误回收,也不用STW,完美。但是屏障技术应用在堆上,并没有应用在栈上,假设a对象分配在栈上,那么当a.B = b1执行时,不会触发写屏障,那可咋弄?
答:在标记阶段需要STW,扫描栈上指针后,并发标记。这种方式虽然也有STW,但范围上已经小了很多,毕竟堆标记的时候没有STW。这种GC方式在go1.5版本使用。

删除写屏障

目标:我们再思考能否从弱三色不变性下手,打破弱三色不变性。即在标记期间如果有指向白色对象的对象更改引用关系,则直接将该白色对象标记为灰色。上面的例子不够看了,我们再换个例子。

场景:
1)a1、b1对象已经标记完毕
2)a2 已经标记完毕,正在标记b2 的 时候,执行a.B = b2,如果没有插入写屏障,则b2仍然未被标记。
3)b2被残忍回收了。
在这里插入图片描述
删除写屏障:当a2 与 b2 解除依赖关系时,触发删除写屏障,b2标记为灰色。

在这里插入图片描述
通过删除写屏障也可以防止内存泄露,但我们还未讨论栈上指针如何处理。

删除写屏障-栈指针处理
1)还是上面的例子,假设a1、a2是栈上对象,上面的例子无法触发删除写屏障,b2内存泄露。
2)a2与b2 解除关系后,b2 与b1进行绑定,如果没有插入写屏障,即使b1是堆对象,也无法触发屏障技术。
在这里插入图片描述
上面两种情况分别讨论下:
1)如果能将a2上的指针对象改为a1,说明a1、a2 在同一个goroutine,每个goroutine有独立的栈空间,同一个goroutine发生指针改变,那么后续对栈进行扫描的时候,可以以协程为单位进行STW,还可以并发扫描,时间少很多。
2)这种情况如果能够引入插入写屏障,b2就可以标记为灰色对象。

混合写屏障

go1.8,实现插入写屏障和删除写屏障,STW期间只需要并发检查每个goroutine对象指针变化情况。大大减少STW的时间。

参考:https://cloud.tencent.com/developer/article/2108449

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明神特烦恼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值