深入理解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)、标记-清除的缺点

标记-清除算法明了,过程鲜明干脆,但是也有非常严重的问题

  1. STW让程序暂停,程序出现卡顿(重要问题
  2. 标记需要扫描整个heap
  3. 清除数据会产生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)、混合写屏障规则

具体操作:

  1. GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW)
  2. GC期间,任何在栈上创建的新对象均为黑色
  3. 堆上被删除的对象标记为灰色
  4. 堆上被添加的对象标记为灰色

混合写屏障实际上满足的是一种变形的弱三色不变式。它的伪代码如下:

添加下游对象(当前下游对象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模式全分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/198334.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7.Vue UI库

7.Vue UI库 7.1移动端常用的UI库 (1) Vant:Vant 4 - A lightweight, customizable Vue UI library for mobile web apps.A lightweight, customizable Vue UI library for mobile web apps.https://vant-ui.github.io/vant/#/zh-CN &#xf…

提升--22---ReentrantReadWriteLock读写锁

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 ReadWriteLock----读写锁1.读写锁介绍线程进入读锁的前提条件:线程进入写锁的前提条件:而读写锁有以下三个重要的特性: Reentran…

个体诊所电子处方系统哪个好用,推荐一款可以自由设置配方模板能够填写病历可以查询历史病历的门诊处方笺软件

一、前言 1、功能实用,操作简单,不会电脑也会操作,软件免安装,已内置数据库。 2、中医西医均可用此软件开电子处方,支持病历记录查询,药品进出库管理,支持配方模板一键导入电子处方。 二、电子…

Amazon CodeWhisperer 正式可用, 并面向个人开发者免费开放

文章作者:深度-围观 北京——2023年4月18日,亚马逊云科技宣布,实时 AI 编程助手 Amazon CodeWhisperer 正式可用,同时推出的还有供所有开发人员免费使用的个人版(CodeWhisperer Individual)。CodeWhisperer…

SAP ABAP Tree Control 对象与ALV Grid 对象关联

Tree Control 对象与ALV Grid 对象关联 在双击 Tree 对象时,变更ALV Trid 对象的显示,实现界面如图9-11 所示。 Screen 设计界面如图9-12 所示。 主程序: REPORT ytest36. DATA: ok_code TYPE sy-ucomm,save_ok TYPE sy-ucomm. DATA: wa_co…

【C++】map和set的使用及注意事项

map和set的使用及注意事项 1.关联式容器2. 键值对3.set3.1接口介绍3.1.1构造3.1.2迭代器3.1.3容量3.1.4修改 3.2set使用及注意事项 4.multiset5.map6.multimap349. 两个数组的交集 1.关联式容器 在初阶阶段,我们已经接触过STL中的部分容器,比如&#xf…

vue项目解决计算后浮点数精度问题

1.1 问题描述 计算出的结果本来应该为13.8386,但是这里因为js精度问题,导致后边多了一串的0000001。 1.2 使用场景 求和,每个物品的单价*数量 1.3 解决办法 引入第三方库Decimal 1.4 vue项目中Decimal安装步骤 1.4.1 安装Decimal np…

C语言枚举详解,typedef简介(能看懂文字就能明白系列)

系列文章目录 C语言基础专栏 笔记详解 🌟 个人主页:古德猫宁- 🌈 信念如阳光,照亮前行的每一步 文章目录 系列文章目录🌈 *信念如阳光,照亮前行的每一步* 前言一、枚举类型的声明枚举常量三、枚举类型的优…

索尼PMW580视频帧EC碎片重组开启方法

索尼PMW580视频帧EC碎片重组开启方法 索尼PMW-580摄像机生成的MXF文件存在严重的碎片化,目前CHS零壹视频恢复程序MXF版、专业版、高级版已经支持重组结构体正常的碎片,同时也支持对于结构体破坏或者覆盖后仅存在音视频帧EC数据的重组,需要注…

开关电源有哪些EMI整改?|深圳比创达电子EMC

某控制产品在进行辐射发射测试时,发现测试结果超标,辐射发射测试结果如下图所示: 控制产品在去掉发射源之前,就在各外部端口采取了各种滤波措施,结果并无明显作用,即使把所有相关外部引线全部拿走(只剩下电…

菜鸟学习日记(python)——运算符

我们进行运算时,需要两类数据,操作数和运算符,例如:ab就是一个运算,它的操作数是a和b,运算符是‘’ 在python中运算符包括以下几大类: 算数运算符比较(关系)运算符赋值…

spark无法执行pi_如何验证spark搭建完毕

在配置yarn环境下的spark时,执行尚硅谷的以下命令发现报错,找不到这个也找不到那个,尚硅谷的代码是 bin/spark-submit \ --class org.apache.spark.examples.SparkPi \ --master yarn \ --deploy-mode cluster \ ./examples/jars/spark-exam…

k8s部署单机模式的minio

k8s部署单机模式的minio 一、说明二、yaml内容三、步骤3.1 创建资源3.2 查看启动日志3.2 查看svc并访问控制台 一、说明 项目使用minio,准备在k8s环境部署一套minio试用。 1.关于minio的原理和概念参考: https://mp.weixin.qq.com/s?__bizMzI3MDM5NjgwNg&mid…

Python中函数添加超时时间,Python中signal使用

from time import time, sleepimport signal# 模拟要删除5条数据,中间有超时的i 5# 超时后执行的方法def timeout_handler(signal, frame):# 引发异常raise TimeoutError("删除第" str(i) "条,超时!")# 或者执行其他操作,不往外抛异常(超时的函数不会被…

使用Spring Cloud搭建一个项目框架之父子模块的创建

前言 说实在的,Spring Cloud的这个系列,很多人都写过,写的比博主好的一大堆,网友们百度一下,就可以发现很多很多,甚至有一批都是系列文章,我只不过之前学过一段时间,但是过了一段时…

ElasticSearch基础知识

ElasticSearch是一个高扩展的分布式全文搜索引擎,基于Lucene作为核心实现所有索引和搜索的功能。 使用场景: (1)搜索领域:如维基百科、谷歌,全文检索等。 (2)网站具体内容&#xf…

聊聊 Jetpack Compose 的 “状态订阅自动刷新” -- mutableStateListOf

Jekpack Compose “状态订阅&自动刷新” 系列: 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - MutableState/mutableStateOf 】 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - remember 和重组作用域 】 【 聊聊 Jetpack Compose 的 …

腾讯云轻量应用服务器怎么安装宝塔Linux面板?

腾讯云轻量应用服务器宝塔面板怎么用?轻量应用服务器如何安装宝塔面板?在镜像中选择宝塔Linux面板腾讯云专享版,在轻量服务器防火墙中开启8888端口号,然后远程连接到轻量服务器执行宝塔面板账号密码查询命令,最后登录和…

洗袜子的洗衣机哪款好?最好用的迷你洗衣机推荐

内衣洗衣机是一种专门用于清洁内衣裤以及袜子等等贴身衣物的小型洗衣机。对于大部分的用户而言,内衣裤作为着我们日常生活中必不可少的贴身衣物,因此对于日常所穿内衣裤的清洁和卫生都是非常重要。不仅涉及到隐私部位的卫生安全,更容易影响自…

javaEE -14(10000字 JavaScript入门 - 1)

一:初始 JavaScript JavaScript (简称 JS)是世界上最流行的编程语言之一,它是一个脚本语言, 通过解释器运,主要在客户端(浏览器)上运行, 现在也可以基于 node.js 在服务器端运行. JavaScript 和 HTML 和 CSS 之间的关系: HTML…