深入理解Go语言GC机制
1、Go 1.3之前的标记-清除(mark and sweep)算法
Go 1.3之前的时候主要用的是普通的标记-清除算法,此算法主要由两个主要的步骤:
- 标记(Mark phase)
- 清除(Sweep phase)
1)、标记-清除算法的具体步骤
第一步,暂停程序业务逻辑,分类出可达和不可达的对象,然后做上标记

图中表示是程序与对象的可达关系,目前程序的可达对象有对象1->2->3,对象4->7等五个对象
第二步,开始标记,程序找出它所有可达的对象,并做上标记

对象1->2->3、对象4->7等五个对象被做上标记
第三步,标记完了之后,然后开始清除未标记的对象

操作非常简单,但是有一点需要额外注意:mark and sweep算法在执行的时候,需要程序暂停,即STW(stop the world)。STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程影响很大,所以STW也是一些回收机制最大的难题和希望优化的点。所以在执行第三步的这段时间,程序会暂停任何工作,卡在那等待回收执行完毕
第四步,停止暂停,让程序继续运行,然后重复这个过程,直到进程生命周期结束
2)、标记-清除的缺点
标记-清除算法明了,过程鲜明干脆,但是也有非常严重的问题
- STW让程序暂停,程序出现卡顿(重要问题)
- 标记需要扫描整个heap
- 清除数据会产生heap碎片
Go 1.3版本之前就是以上来实施的,在执行GC的基本流程就是首先启动STW暂停,然后执行标记,再执行数据回收,最后停止STW
从上图来看,全部的GC时间都是包裹在STW范围之内的,这样貌似程序暂停的时间过长,影响程序的运行性能。所以Go 1.3做了简单的优化,将STW的步骤提前,减少STW暂停的时间范围
上图主要是将STW的步骤提前了一步,因为在sweep清除的时候,可以不需要STW停止,因为这些对象已经是不可达对象了,不会出现回收写冲突等问题
但是无论怎么优化,Go 1.3都面临这个一个重要问题,就是标记-清除算法会暂停整个程序
2、Go 1.5的三色并发标记法
Go中的垃圾回收主要应用三色标记法,GC过程和其他用户goroutine可并发运行,但需要一定时间的STW,所谓三色标记法实际上就是通过三个阶段的标记来确定清除的对象都有哪些
1)、三色标记法的过程
第一步,每次新创建的对象,默认的颜色都是标记为白色

如上图所示,左边为程序可抵达的内存对象关系,右边的标记表用来记录目前每个对象的标记颜色分类。这里需要注意的是,所谓程序则是一些对象的根结点集合。所以我们如果将程序展开,会得到类似如下图的表现形式

第二步,每次GC回收开始,会从根节点开始遍历所有对象,把遍历到的对象从白色集合放入灰色集合

这里要注意的是,本次遍历是一次遍历,非递归形式,是从程序抽出可抵达的对象遍历一层,如上图所示,当前可抵达的对象是对象1和对象4,那么自然本轮遍历结束,对象1和对象4就会被标记为灰色,灰色标记表就会多出这两个对象
第三步,遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合

这一次遍历是只扫描灰色对象,将灰色对象的第一层遍历可抵达的对象由白色变为灰色,如对象2、对象7。而之前的灰色对象1和对象4则会被标记为黑色,同时由灰色标记表移动到黑色标记表中
第四步,重复第三步,直到灰色中无任何对象


当全部的可达对象都遍历完后,灰色标记表将不再存在灰色对象,目前全部内存的数据只有两种颜色,黑色和白色。那么黑色对象就是我们程序逻辑可达(需要的)对象,这些数据是目前支撑程序正常业务运行的,是合法的有用数据,不可删除,白色的对象是全部不可达对象,目前程序逻辑并不依赖它们,那么白色对象就是内存中目前的垃圾数据,需要被清除
第五步,回收所有的白色标记表的对象,也就是垃圾回收

将全部的白色对象进行删除回收,剩下的就是全部依赖的黑色对象
以上便是三色并发标记法,不难看出,上面已经清楚的体现三色的特性。但是这里面可能会有很多并发流程均会被扫描,执行并发流程的内存可能相互依赖,为了在GC过程中保证数据的安全,我们在开始三色标记之前就会加上STW,在扫描确定黑白对象之后再放开STW。但是很明显这样的GC扫描的性能实在是太低了
2)、没有STW的三色标记法
假设没有STW,那么也就不会再存在性能上的问题。那么接下来假设如果三色标记法不加入STW会发生什么事情?
当前已经经历了第一轮扫描,目前黑色的有对象1和对象4,灰色的有对象2和对象7,其他的为白色对象,且对象2是通过指针p指向对象3的,如下图所示:

现在如果三色标记过程不启动STW,那么在GC扫描过程中,任意的对象均可能发生读写操作,如下图所示,在还没有扫描到对象2的时候,已经标记为黑色的对象4,此时创建指针q,并且指向白色的对象3

与此同时灰色的对象2将指针p移除,那么白色的对象3实则就是被挂在了已经扫描完成的黑色的对象4下,如下图所示:

然后正常执行三色标记的算法逻辑,将所有灰色的对象标记为黑色,那么对象2和对象7就被标记为了黑色,如下图所示:

那么就执行了三色标记的最后一步,将所有白色对象当做垃圾进行回收,如下图所示:

但是本来是对象4合法引用的对象3却被GC给误杀回收掉了
3)、触发三色标记法不安全的必要条件
可以看出,有两种情况在三色标记法中是不希望被发生的
- 条件1:一个白色对象被黑色对象引用(白色被挂在黑色下)
- 条件2:灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色)
如果当以上两个条件同时满足时,就会出现对象丢失现象
并且,如图所示的场景中,如果示例的白色对象3还有很多下游对象的恶化,也会一并都清理掉
为了防止这种现象的发生,最简单的方式就是STW,直接禁止掉其他用户程序对对象引用关系的干扰,但是STW的过程有明显的资源浪费,对所有的用户程序都有很大影响。那么是否可以在保证对象不丢失的情况下合理的尽可能的提高GC效率,减少STW时间呢?答案是可以的,只要使用一种机制,尝试去破坏上面的两个必要条件就可以了
3、屏障机制
让GC回收器满足下面两种情况之一时,即可保证对象不丢失。这两种方式就是强三色不变式和弱三色不变式
1)、强弱三色不变式
强三色不变式:
不存在黑色对象引用到白色对象的指针

强三色不变式实际上是强制性的不允许黑色对象引用白色对象,这样就不会出现有白色对象被误删的情况
弱三色不变式:
所有被黑色对象引用的白色对象都处于灰色保护状态

弱三色不变式强调,黑色对象可以引用白色对象,但是这个白色对象必须存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象。这样实则是黑色对象引用白色对象,白色对象处于一个危险被删除的状态,但是上游灰色对象的引用,可以保护该白色对象,使其安全
为了遵循上述的两个方式,GC算法演进到两种屏障方式:插入屏障、删除屏障
2)、插入屏障
具体操作:在A对象引用B对象的时候,B对象被标记为灰色(将B挂在A下游,B必须被标记为灰色)
满足:强三色不变式(不存在黑色对象引用到白色对象的情况了,因为白色会强制变为灰色)
伪代码:
添加下游对象(当前下游对象slot, 新下游对象ptr) { // 1标记灰色(新下游对象ptr) // 2当前下游对象slot = 新下游对象ptr
}
场景:
A.添加下游对象(nil, B) // A之前没有下游,新添加一个下游对象B,B被标记为灰色
A.添加下游对象(C, B) // A将下游对象C更换为B,B被标记为灰色
这段伪代码逻辑就是写屏障。黑色对象的内存槽有两种位置,栈和堆。栈空间的特点是容量小,但是要求响应速度快,因为函数调用弹出频繁使用,所以插入屏障机制在栈空间的对象操作中不使用,而仅仅使用在堆空间对象的操作中






但是如果栈不添加,当全部三色标记扫描之后,栈上有可能依然存在白色对象被引用的情况(如上图的对象9)。所以要对栈重新进行三色标记扫描,但这次为了对象不丢失,要对本次扫描启动STW暂停,直到栈空间的三色标记结束



最后将栈和堆空间扫描剩余的全部白色节点清除。这次STW大约的时间在10-100ms间

3)、删除屏障
具体操作:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色
满足:弱三色不变式(保护灰色对象到白色对象的路径不会断)
伪代码:
添加下游对象(当前下游对象slot, 新下游对象ptr) {// 1if (当前下游对象slot是灰色 || 当前下游对象slot是白色) {标记灰色(当前下游对象slot) // slot为被删除对象,标记为灰色}// 2当前下游对象slot = 新下游对象ptr
}
场景:
A.添加下游对象(B, nil) // A对象删除B对象的引用.B被A删除,被标记为灰(如果B之前为白)
A.添加下游对象(B, C) // A对象更换下游B变成C.B被A删除,被标记为灰(如果B之前为白)







这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉
4、Go 1.8的混合写屏障(hybrid write barrier)机制
插入屏障和删除屏障的短板:
- 插入屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活
- 删除屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象
Go 1.8版本引入了混合写屏障机制(hybrid write barrier),避免了对栈re-scan的过程,极大的减少了STW的时间,结合了两者的优点
1)、混合写屏障规则
具体操作:
- GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW)
- GC期间,任何在栈上创建的新对象均为黑色
- 堆上被删除的对象标记为灰色
- 堆上被添加的对象标记为灰色
混合写屏障实际上满足的是一种变形的弱三色不变式。它的伪代码如下:
添加下游对象(当前下游对象slot, 新下游对象ptr) {// 1 标记灰色(当前下游对象slot) // 只要当前下游对象被移走,就标记灰色// 2 标记灰色(新下游对象ptr)// 3当前下游对象slot = 新下游对象ptr
}
注意:屏障技术不在栈上应用,因为要保证栈的运行效率。混合写屏障是GC的一种屏障机制,所以只是当程序执行GC的时候,才会触发这种机制
2)、混合写屏障的具体场景分析
1)GC开始:扫描栈区,将可达对象全部标记为黑


2)场景一: 对象被一个堆对象删除引用,成为栈对象的下游
场景1主要描述的是对象被堆对象删除引用,成为栈对象的下游情况,伪代码如下:
// 前提:堆对象4->对象7 = 对象7; // 对象7被对象4引用
栈对象1->对象7 = 堆对象7; // 将堆对象7挂在栈对象1下游
堆对象4->对象7 = null; // 对象4删除引用对象7


3)场景二: 对象被一个栈对象删除引用,成为另一个栈对象的下游
场景2主要描述的是对象被一个栈对象删除引用,成为另一个栈对象的下游情况,伪代码如下:
new 栈对象9;
对象8->对象3 = 对象3; // 将栈对象3挂在栈对象9下游
对象2->对象3 = null; // 对象2删除引用对象3



4)场景三:对象被一个堆对象删除引用,成为另一个堆对象的下游
场景3主要描述的是对象被一个堆对象删除引用,成为另一个堆对象下游的情况,伪代码如下:
堆对象10->对象7 = 堆对象7; // 将堆对象7挂在堆对象10下游
堆对象4->对象7 = null; // 对象4删除引用对象7



5)场景四:对象从一个栈对象删除引用,成为另一个堆对象的下游
场景4主要描述的是对象从一个栈对象被删除引用,成为另一个堆对象的下游情况,伪代码如下:
堆对象10->对象7 = 堆对象7; // 将堆对象7挂在堆对象10下游
堆对象4->对象7 = null; // 对象4删除引用对象7



Go中的混合写屏障满足弱三色不变式,结合了删除屏障和插入屏障的优点,只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间
6、小结
Go 1.3使用普通标记清除法,整体过程需要启动STW,效率极低
Go 1.5使用三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通
Go 1.8使用三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高
参考:
Golang三色标记混合写屏障GC模式全分析
相关文章:

深入理解Go语言GC机制
1、Go 1.3之前的标记-清除(mark and sweep)算法 Go 1.3之前的时候主要用的是普通的标记-清除算法,此算法主要由两个主要的步骤: 标记(Mark phase)清除(Sweep phase) 1)…...

qt-C++笔记之组件-分组框QGroupBox
qt-C笔记之组件-分组框QGroupBox code review! 文章目录 qt-C笔记之组件-分组框QGroupBox1.《Qt 6 C开发指南》p752.《Qt 官方文档》3.《Qt 5.12实战》——5.9 分组框控件 1.《Qt 6 C开发指南》p75 2.《Qt 官方文档》 中间段落翻译: 我把示例补充完整: …...
qt 定时器用法
在qt开发中,定时器是我们经常用到的。我们接下来说一下定时器的三种用法,需要注意的是定时器事件是在主线程中触发的,因此在处理耗时操作时应特别小心,以避免阻塞应用程序的事件循环。 1. 三种定时器使用 1.1 QObject的定时器 …...

用23种设计模式打造一个cocos creator的游戏框架----(九)访问者模式
1、模式标准 模式名称:访问者模式 模式分类:行为型 模式意图:将数据操作与数据结构分离,使得在不修改数据结构的前提下,可以添加或改变对数据的操作。 结构图: 适用于: 当你需要对一个复杂对…...

根文件系统初步测试
一. 简介 上一篇文章学习了向所编译生成的根文件系统中加入 lib库文件。文章地址如下: 根文件系统lib库添加与初步测试-CSDN博客 本文继上一篇文章的学习,本文对之前制作的根文件系统进行一次初步测试。 二. 根文件系统初步测试 为了方便测试&#…...

【精选】设计模式——策略设计模式-两种举例说明,具体代码实现
Java策略设计模式 简介 策略设计模式是一种行为型设计模式,它允许在运行时选择算法的行为。 在软件开发中,我们常常需要根据不同情况采取不同的行为。通常的做法是使用大量的条件语句来实现这种灵活性,但这会导致代码变得复杂、难以维护和扩…...

外包干了3个月,技术倒退2年。。。
先说情况,大专毕业,18年通过校招进入湖南某软件公司,干了接近6年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...
微信小程序:chooseimage从本地相册选择图片或使用相机拍照
文档 https://uniapp.dcloud.net.cn/api/media/image.html#chooseimage https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.chooseImage.html 代码示例 const res await uni.chooseImage({count: 1, //默认9sizeType: [original, compressed], //可以…...

「Swift」取消UITableView起始位置在状态栏下方开始
前言:在写页面UI时发现,当隐藏了NavigationBar时,即使UITableView是从(0,0)进行布局,也会一直在手机状态栏下方进行展示布局,而我的想法是希望UITableView可以从状态栏处就进行展示布局 当前页面展示: 问题…...
android高版本适配使用Tools.java
随着android版本的提升,原生Tools不公开并且不能被正常使用,为了延续项目的功能,修改如下: /** Copyright (C) 2006 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License&quo…...

面试官:说说webpack中常见的Loader?解决了什么问题?
面试官:说说webpack中常见的Loader?解决了什么问题? 一、是什么 loader 用于对模块的"源代码"进行转换,在 import 或"加载"模块时预处理文件 webpack做的事情,仅仅是分析出各种模块的依赖关系&a…...

【蓝桥杯省赛真题50】Scratch智能计价器 蓝桥杯scratch图形化编程 中小学生蓝桥杯省赛真题讲解
目录 scratch智能计价器 一、题目要求 编程实现 二、案例分析 1、角色分析...
折半查找(数据结构实训)
题目: 标准输入输出 题目描述: 实现折半查找。要求查找给定的值在数据表中相应的存储位置。本题目假定输入元素均按非降序输入。 输入: 输入包含若干个测试用例,第一行为测试用例个数k。每个测试用例占3行,其中第一行为…...

AR助推制造业智能转型:实时远程协作与可视化引领生产创新
制造商面临着多方面的变革,技术的兴起催生了工业物联网(IIoT),改变了现代工厂的外貌、系统和流程。同时,全球竞争压力和不断变化的员工队伍要求采用新的员工培训方法,并重新审视工人在工厂中的角色。尽管如…...

【用unity实现100个游戏之18】从零开始制作一个类CSGO/CS2、CF第一人称FPS射击游戏——基础篇3(附项目源码)
文章目录 本节最终效果前言素材人物移动音效枪口火焰和开火音效枪口灯光弹孔和火花添加武器随镜头手臂摇摆效果源码完结 本节最终效果 前言 本节主要实现添加音效,和一些特效、武器摆动调整。 素材 素材,为了方便我直接用了unity免费的音效输出&#…...

sed 流式编辑器
使用方式: 1,前置指令 | sed 选项 定址符指令 2,sed 选项 定址符指令 被处理文档 选项: -n 屏蔽默认输出 -i写入文件 -r支持扩展正则 指令: p输出 d删除 s替换 sed -n 1p user //输出第1行 sed -n…...

Linux shell编程学习笔记33:type 命令
目录 0 引言1 type 命令的功能和格式 1.1 type命令的功能1.2 type 命令的格式2 type命令用法实例 2.1用type命令查看shell内置命令(以echo命令为例)2.2 用type命令查看别名(以ls命令为例)2.3 用type命令同时查看shell内置命令和别…...

【数据结构】—红黑树(C++实现)
🎬慕斯主页:修仙—别有洞天 💜本文前置知识: AVL树 ♈️今日夜电波:Letter Song—ヲタみん 1:36━━━━━━️💟──────── 5:35 …...

内衣洗衣机和手洗哪个干净?高性价比内衣洗衣机推荐
通常来说,我们的内衣裤对卫生要求比较高,毕竟是贴身穿的,所以如果和一般的衣物一起洗,就怕会有细菌互相感染。所以很多用户为了内衣裤的卫生都会选择自己手动洗,但手洗一方面很费时间和人力,另一方面又很伤…...

TikTok与互动广告:品牌如何打破传统界限
随着数字时代的蓬勃发展,广告行业也经历了翻天覆地的变革。在这个变革的浪潮中,TikTok作为一款崭新的社交媒体平台,通过其独特的短视频形式为品牌提供了全新的互动广告机会。 本文将深入探讨TikTok与互动广告的结合,以及品牌如何…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...

五子棋测试用例
一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏,有着深厚的文化底蕴。通过将五子棋制作成网页游戏,可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家,都可以通过网页五子棋感受到东方棋类…...

21-Oracle 23 ai-Automatic SQL Plan Management(SPM)
小伙伴们,有没有迁移数据库完毕后或是突然某一天在同一个实例上同样的SQL, 性能不一样了、业务反馈卡顿、业务超时等各种匪夷所思的现状。 于是SPM定位开始,OCM考试中SPM必考。 其他的AWR、ASH、SQLHC、SQLT、SQL profile等换作下一个话题…...
生成对抗网络(GAN)损失函数解读
GAN损失函数的形式: 以下是对每个部分的解读: 1. , :这个部分表示生成器(Generator)G的目标是最小化损失函数。 :判别器(Discriminator)D的目标是最大化损失函数。 GAN的训…...

浏览器工作原理01 [#]Chrome架构:仅仅打开了1个页面,为什么有4个进程
引用 浏览器工作原理与实践 Chrome打开一个页面需要启动多少进程?你可以点击Chrome浏览器右上角的“选项”菜单,选择“更多工具”子菜单,点击“任务管理器”,这将打开Chrome的任务管理器的窗口,如下图 和Windows任务管…...