聊聊Go的三色标记法( 二 )
其实这三种颜色是啥不重要的 , 重要的是它们所表达的状态 , 灰色的中间状态 , 标记过程结束后只会存在白色或者黑色 。
整个过程中 , 这些状态是如下图这样变化的 。
看似很完美的解决方案 , 其实也存在的一个问题:标记过程中 , 对象引用发生了变化 。
它会导致两个问题 , 「多标」和「漏标」 。
多标就是下图这样:
文章图片
文章图片
由于步骤2不会STW , 所以可能存在扫描过A将它标记为黑色后 , 又重新引用了一个原本已经被标记为白色的D(C断开了与D的引用) 。此时 , D就会被回收掉 , 导致程序出现意料之外的bug 。
「漏标」就是这样:
文章图片
文章图片
对象 E/F/G 是“应该”被回收的 。然而因为 E 已经变为灰色了 , 其仍会被当作存活对象继续遍历下去 。最终的结果是:这部分对象仍会被标记为存活 , 即本轮 GC 不会回收这部分内存 。
传统的解决这两个问题的思路有两个:在断开引用的时候做额外处理 。在「黑色」对象重新建立「白色」对象的引用时做额外处理 。(回收开始后新建的对象默认为黑色) 。
第一个思路专业叫法是「写屏障」 , 第二个是「读屏障」 。其实名字就是噱头 , 你可以把它们俩当我们平时编程中用到的 AOP 概念来理解 , 在修改和读取之前做一些操作 。
基于「写屏障」 , 可以延伸出两个方案:增量更新(Incremental Update) 。针对新增的引用 , 将其记录下来等待重新遍历 。这个操作在「修改操作后」进行 , JVM 中的 CMS 垃圾回收器就是这个思路 。原始快照(Snapshot At The Beginning , SATB) 。当某个时刻 的 GC Roots 确定后 , 当时的对象图就已经确定了 。如果期间发生变化 , 则可以记录起来 , 保证标记依然按照原本的视图来 。这个操作在「修改操作前」进行 , JVM中 的 G1 垃圾回收器用的就是这个思路 。理论上 , 配合 「Remembered Set」 , SATB 的效率是比增量更新要高的 , 不过会消耗更多的内存 。
基于「读屏障」的方案是:在「黑色」对象重新建立「白色」对象的引用前 , 将这个白色对象记录下来 , 避免被回收掉 。这个动作在「读取操作前」进行 , JVM 中的 ZGC 垃圾回收器就是这个思路 。
在 Golang(1.8版本之后)里 , 用的是一种新的机制 , 称之为「混合写屏障」机制 。它的思路总结下来就是4句话:将对象分为堆上的对象和栈上的对象 。GC 开始将栈上的对象全部扫描并标记为黑色 , 无需 STW 。并且之后不再进行第二次重复扫描在 GC 期间 , 任何在栈上创建的新对象 , 均为黑色 。在 GC 期间 , 在堆上被删除或者添加的对象都标记为灰色 。后续继续扫描 。
你看 , 其实这些原理也没那么复杂 , 我相信只要你搞清楚了自己面对的是什么问题 , 你也能想到这些方案 。
好了 , 总结一下 。
这篇呢 , Z 哥和你分享了我对 Golang 中的 GC 机制「三色标记法」的了解 。
GC 的底层判断对象存活思路主要是两个 , 引用计数和可达性分析 。由于引用计数存在循环引用问题 , 所以大多数 GC 都是按照后者的思路实现的 , Golang 也不例外 。
「三色标记法」的原理是 , 将对象分为了三种状态:白色 , 默认值 。本次回收没被扫描过的对象都是白色的 。确认不可达的对象也是白色 , 但是会被标记「不可达」 。灰色 , 中间状态 。本对象有被外部引用 , 但是本对象引用的其它对象尚未全部检测完 。黑色 , 本对象有被其它对象引用 , 且已检测完本对象引用的其它对象 。
- 智库论坛 | 社区电商推动供应链数字化转型的对策建议
- 人类与AI如何共处?诺奖科学家、将棋天才、“低欲望社会”提出者的不同解答
- 线上新书发布!云上带你了解有关“冬奥”的那些事儿
- 我的世界大闹天宫龙宫怎么玩(我的世界大闹天宫龙宫打法技巧一览)
- 复苏的魔女竞技场用什么队伍(复苏的魔女竞技场队伍搭配心得分享)
- 复苏的魔女魔导装备boss怎么打(复苏的魔女魔导妨害装备获取攻略大全)
- 今年的iphonese,还是你熟悉的模样
- 长安福特蒙迪欧在中国市场的“全球化”
- safari浏览器中的indexeddb漏洞
- 复苏的魔女时空裂隙用什么阵容好(复苏的魔女时空裂隙阵容搭配指南)
