【golang】深入理解Go语言垃圾回收(GC)

news2024/11/18 17:50:03

垃圾回收

  • 垃圾回收
    • 版本1.3之前标记-清除(mark and sweep)算法
      • 标记-清除(mark and sweep)的缺点
    • 版本1.5的三色并发标记法
    • 没有STW的三色标记法
    • 屏障机制
      • 强-弱 三色不等式
      • 插入屏障
      • 删除屏障
    • 版本1.8的混合写屏障(hybrid write barrier)机制
      • 混合写屏障机制

垃圾回收

垃圾回收(Garbage Collection,简称GC)是编程语言中提供的自动的内存管理机制,自动释放不需要的对象,让出存储器资源,无需程序员手动执行。

版本1.3之前标记-清除(mark and sweep)算法

步骤:

  • 标记(Mark phase)
  • 清除(Sweep phase)

总体思想就是:暂停程序业务逻辑,找出不可达的对象,然后做上标记。第二步,回收标记好的对象。

  1. mark and sweep算法在执行的时候,需要程序暂停!即STW(stop the world)。也就是说,这段时间程序会卡在哪儿。

  2. 开始标记,程序找出它所有可达的对象,并做上标记。如下图所示:
    在这里插入图片描述

  3. 标记完了之后,然后开始清除未标记的对象,结果如下:
    在这里插入图片描述

  4. 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。

标记-清除(mark and sweep)的缺点

  • STW,stop the world;让程序暂停,程序出现卡顿(重要问题)
  • 标记需要扫描整个heap
  • 清除数据会产生heap碎片

版本1.3之前就是以上来实施的,流程为

在这里插入图片描述
版本1.3做了简单的优化,将STW提前,减少STW暂停的时间范围,如下所示

在这里插入图片描述

版本1.5的三色并发标记法

三色标记法 实际上就是通过三个阶段的标记来确定清楚的对象都有哪些。

具体步骤:

  1. 新创建的对象,默认的颜色都是标记为”白色“
    在这里插入图片描述
  2. 每次GC回收开始,然后从根节点开始遍历所有对象,把遍历到的对象从白色集合放入灰色集合。
    在这里插入图片描述
  3. 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合。
    在这里插入图片描述
  4. 重复第三步,直到灰色中无任何对象
  5. 回收所有的白色标记表的对象,也就是回收垃圾
    在这里插入图片描述

Go是如何解决标记-清除算法中的卡顿STW问题的呢?

没有STW的三色标记法

如果三色标记法,标记法过程中不使用STW将会发生什么呢?

  1. 已经标记为灰色的对象2,有指针p指向白色的对象3
    在这里插入图片描述
  2. 在还没有扫描到对象2,已经标注为黑色的对象4,创建指针q,指向对象3
    在这里插入图片描述
  3. 于此同时对象2将指针p移除,对象3就被挂在了已经扫描完成的黑色的对象4下。
    在这里插入图片描述
  4. 正常执行算法逻辑,对象2,7标记为黑色,而对象3因为对象4已经不会再扫描,而等待被回收清除。
    在这里插入图片描述
  5. 对象3,这个被正常引用的对象,被无辜的清除掉了。

可以看出,有两个问题,在三色标记法是不希望被发生的

  • 条件1:一个白色对象被黑色对象引用(白色被挂在黑色下)
  • 条件2:灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色)

以上两个条件同时满足时,就会出现对象丢失现象。

为了防止这种现象的发生,最简单的方式就是STW,直接禁止掉其他用户程序对对象引用关系的干扰,但是STW的过程有明显的资源浪费,对所有的用户程序都有很大影响,如何能在保证对象不丢失的情况下合理的尽可能的提高GC效率,减少STW时间呢?

​ 答案就是, 那么我们只要使用一个机制,来破坏上面的两个条件就可以了.

屏障机制

破坏上面两个条件引出两种方式:

强-弱 三色不等式

  • 强三色不等式

不存在黑色对象引用到白色对象的指针。
在这里插入图片描述

  • 弱三色不等式

所有被黑色对象引用的白色对象都处于灰色保护状态

在这里插入图片描述

为了遵循上述的两个方式Go团队初步得到了如下具体的两种屏障方式“插入屏障”,“删除屏障”。

插入屏障

具体操作:在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被标记为灰色

​这段伪码逻辑就是写屏障,我们知道,黑色对象的内存槽有两种位置, .栈空间的特点是容量小,但是要求相应速度快,因为函数调用弹出频繁使用, 所以“插入屏障”机制,在栈空间的对象操作中不使用,而仅仅使用在堆空间对象的操作中。

  1. 程序起初创建,全部标记为白色,将所有对象放入白色对象中。
    在这里插入图片描述

  2. 遍历Root Set(非递归形式,只遍历一次),得到灰色节点
    在这里插入图片描述

  3. 遍历Grey灰色标记表,将可达的对象,从白色标记为灰色,遍历之后的灰色,标记为黑色
    在这里插入图片描述

  4. 由于并发特性,此刻外界相对象4添加对象8、对象1添加对象9,对象4在堆区,即将触发插入屏障机制,对象1不触发
    在这里插入图片描述

  5. 由于插入写屏障(黑色对象添加白色,将白色改为灰色),对象8变成灰色,对象9依然为白色
    在这里插入图片描述

  6. 继续循环上述流程进行三色标记,直到没有灰色节点
    在这里插入图片描述
    但是如果栈不添加,当全部三色标记扫描之后,栈上有可能依然存在白色对象被引用的情况(如上图对象中的9),所以要对栈重新进行三色标记扫描, 但这次为了对象不丢失, 要对本次标记扫描启动STW暂停. 直到栈空间的三色标记结束.

  7. 在准备回收白色前,重新遍历扫描一次栈空间。此时加STW暂停保护栈,防止外界干扰(有新的白色被黑色添加)
    在这里插入图片描述

  8. 在STW中,将栈中的对象一次三色标记,直到没有灰色节点
    在这里插入图片描述

  9. 停止STW
    在这里插入图片描述

  10. 清除白色
    在这里插入图片描述

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

删除屏障

具体操作:被删除的对象,如果自身为灰色或白色,那么标记为灰色。
满足弱三色不等式(保护灰色对象到白色对象的路径不会断)

伪代码:

添加下游对象(当前下游对象slot, 新下游对象ptr) {
  //1
  if (当前下游对象slot是灰色 || 当前下游对象slot是白色) {
          标记灰色(当前下游对象slot)     //slot为被删除对象, 标记为灰色
  }

  //2
  当前下游对象slot = 新下游对象ptr
}

场景:

A.添加下游对象(B, nil) //A对象,删除B对象的引用。 B被A删除,被标记为灰(如果B之前为白)
A.添加下游对象(B, C) //A对象,更换下游B变成C。 B被A删除,被标记为灰(如果B之前为白)

  1. 程序起初创建,全部标记为白色,将所有对象放入白色集合中。
    在这里插入图片描述
  2. 遍历Root Set(非递归形式,只遍历一次)得到灰色节点
    在这里插入图片描述
  3. 灰色对象1删除对象5,如果不触发删除写屏障,5-2-3路径与主链路断开,最后均会被清除
    在这里插入图片描述
  4. 触发删除写屏障 被删除的对象5,自身被标记为灰色
    在这里插入图片描述
  5. 遍历Grey 灰色标记表,将可达的对象,从白色标记为灰色,遍历之后的灰色,标记为黑色
    在这里插入图片描述
  6. 继续循环上述流程进行三色标记,直到没有灰色节点
    在这里插入图片描述
  7. 清除白色
    在这里插入图片描述

这种方式的回收精度低,一个对象即使被删除了最后一个指向它的指针也依旧可以活过这一轮,在下一轮GC中被清理掉。

版本1.8的混合写屏障(hybrid write barrier)机制

插入写屏障和删除写屏障的短板:

  • 插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活
  • 删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。

1.8版本引入了混合写屏障机制,避免了对栈re-scan的过程,极大的减少了STW的时间。结合了两者的优点。

混合写屏障机制

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

注意:3、4点是触发写屏障,而触发写屏障只发生在堆结构中。

伪代码:

添加下游对象(当前下游对象slot, 新下游对象ptr) {
      //1 
        标记灰色(当前下游对象slot)    //只要当前下游对象被移走,就标记灰色

      //2 
      标记灰色(新下游对象ptr)

      //3
      当前下游对象slot = 新下游对象ptr
}

GC开始:扫描栈区,将可达对象全部标记为黑

  1. GC刚刚开始,默认都为白色
    在这里插入图片描述
  2. 三色标记法,优先扫描全部栈对象,将可达对象均标记为黑。
    在这里插入图片描述

场景一:对象被一个堆对象删除引用,成为栈对象的下游

//前提:堆对象4->对象7 = 对象7; //对象7 被 对象4引用
栈对象1->对象7 = 堆对象7; //将堆对象7 挂在 栈对象1 下游
堆对象4->对象7 = null; //对象4 删除引用 对象7

在这里插入图片描述
在这里插入图片描述
场景二: 对象被一个栈对象删除引用,成为另一个栈对象的下游

new 栈对象9;

对象8->对象3 = 对象3; //将栈对象3 挂在 栈对象9 下游 对象2->对象3 = null; //对象2
删除引用 对象3

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
场景三:对象被一个堆对象删除引用,成为另一个堆对象的下游

堆对象10->对象7 = 堆对象7; //将堆对象7 挂在 堆对象10 下游

堆对象4->对象7 = null; //对象4 删除引用 对象7

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

场景四:对象从一个栈对象删除引用,成为另一个堆对象的下游

堆对象10->对象7 = 堆对象7; //将堆对象7 挂在 堆对象10 下游

堆对象4->对象7 = null; //对象4 删除引用 对象7

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Golang中的混合写屏障满足弱三色不变式,结合了删除写屏障和插入写屏障的优点,只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间。

总结:

  • GoV1.3-普通标记清楚法,整体过程需要启动STW,效率极低
  • GoV1.5-三色标记法,堆空间启动写屏障,栈空间不启动,全部扫描后,需要重新扫描一次栈(需要STW),效率普通
  • GoV1.8-三色标记法,混合写屏障,栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高

文章参考:刘丹冰–《Go修养之路》

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

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

相关文章

MQ - 25 RabbitMQ集群架构设计与实现

文章目录 导图集群构建节点发现元数据存储数据可靠性镜像队列仲裁队列安全控制传输加密身份认证资源鉴权可观测性总结导图 集群构建 集群构建由节点发现和元数据存储两部分组成。RabbitMQ 也是一样的实现思路。 节点发现 在节点发现方面,RabbitMQ 通过插件化的方式支持了多…

经典题记录 字符串相加/相乘

1. LeetCode 415 字符串相加 代码一:代码简短,但需要借助额外的一个string来保存结果,更占用内存。 class Solution { public:string addStrings(string num1, string num2) {string ans"";int size1num1.size();int size2num2.si…

php之导入导出csv文件

一、导入csv文件 1、创建导入页面 <!DOCTYPE html> <html lang"zh-CN"> <meta charset"UTF-8"> <head><title>文件提交表单</title> </head> <body> <form action"test5.php" method"…

嵌入式裸机架构的探索与崩塌

为什么会想着探索下嵌入式裸机的架构呢&#xff1f;是因为最近写了一个项目&#xff0c;项目开发接近尾声时&#xff0c;发现了一些问题&#xff1a; 1、项目中&#xff0c;驱动层和应用层掺杂在一起&#xff0c;虽然大部分是应用层调用驱动层&#xff0c;但是也存在驱动层调用…

6年Android开发前10月的总结,写给正在求职的安卓开发

进入大厂工作对许多人来说已经是一种挑战&#xff0c;但只要充分准备&#xff0c;很多问题都可以逐步解决。当然&#xff0c;运气也起到了一定的作用&#xff0c;但最终还是与自身的努力密不可分。运气是实力的一部分&#xff0c;因为自助者天助。 每到10月进行总结时&#xff…

华为ICT——第二章-数字图像处理私人笔记

目录 1&#xff1a;计算机视觉&#xff1a;​编辑 2&#xff1a;计算机视觉应用&#xff1a;​编辑 3&#xff1a;计算机视界核心问题&#xff1a;​编辑 4&#xff1a;相关学科&#xff1a; 5&#xff1a;计算机视觉与人工智能&#xff1a; 最成熟的技术方向是图像识别 6…

Django应用及分布式路由

Django应用及分布式路由 应用 应用在Django项目中一个完全独立的业务模块&#xff0c;可以包含自己的路由&#xff0c;视图&#xff0c;模板&#xff0c;模型 应用配置 在这里面添加你自定义的应用 INSTALLED_APPS [django.contrib.admin,django.contrib.auth,django.cont…

如何使用docker快速部署MinDoc文档系统

MinDoc是非常优秀的知识分享系统&#xff0c;但是很多刚接触的人会一脸懵逼&#xff0c;而且官方文档写的也并不清晰&#xff0c;所以和大家分享一下快速部署MinDoc的方法。 首先docker环境先自行安装好&#xff0c;这里不再赘述。 拉取docker镜像&#xff1a; docker pull …

解决 Github port 443 : Timed out

解决方法 打开代理页面 打开 设置 --> 网络与Internet --> 查找代理 记录下当前系统代理的 IP 地址和端口号 如上图所示&#xff0c;地址与端口号为&#xff1a;127.0.0.1:7890 注意修改成自己的IP和端口号 git config --global http.proxy http://127.0.0.1:7890 gi…

面试总结之微服务篇

一、概览 1、微服务常用组件 微服务给系统开发带来了诸多问题和挑战&#xff0c;如服务间通信和调用的复杂性、数据一致性和事务管理、服务治理和版本管理等&#xff0c;为解决应对这些问题和挑战&#xff0c;各种微服务组件应运而生微服务的常见组件和实现&#xff1a; 1…

解决react报错“JSX 表达式必须具有一个父元素“

现象如下&#xff1a; 原因&#xff1a; 新插入的dom元素跟已有的dom元素平级了&#xff0c;必须创建一个共有的根元素 解决办法&#xff1a; 使用<> </>标签作为根元素&#xff0c;把所有子元素包裹起来 <> ....原代码 </> 问题解决&#xff01;…

sun.reflect.annotation.TypeNotPresentExceptionProxy

解决方法 找到TypeNotPresentExceptionProxy类,在sun.reflect.annotation下在TypeNotPresentExceptionProxy方法里打断点debug项目,查看断点错误 如图是缺少redis依赖导致的错误 https://www.cnblogs.com/qingmuchuanqi48/p/11716706.html

Spring学习笔记7 Bean的生命周期

Spring其实就是一个管理Bean对象的工厂.它负责对象的创建,对象的销毁. 这样我们才可以知道在哪个时间节点上调用了哪个类的哪个方法,知道代码该写在哪里 Bean的生命周期之粗略5步 Bean生命周期的管理可以参考Spring的源码: AbstractAutowireCapableBeanFactory Bean的生命周期…

乐得瑞推出1拖2功率分配快充线方案,支持数据传输

随着PD3.1协议的市场应用越来越多&#xff0c;一些充电器的Type-C接口的输出功率达到百瓦及以上&#xff0c;如何充分利用好这类充电器设备&#xff0c;乐得瑞电子推出1拖2快充线缆解决方案&#xff0c;支持智能功率分配策略。 上图是乐得瑞1拖2功率分配快充线样线实物&#…

力扣刷题-链表-设计链表

题意&#xff1a; 在链表类中实现这些功能&#xff1a; get(index)&#xff1a;获取链表中第 index 个节点的值。如果索引无效&#xff0c;则返回-1。 addAtHead(val)&#xff1a;在链表的第一个元素之前添加一个值为 val 的节点。插入后&#xff0c;新节点将成为链表的第一个节…

哈希 -- 开散列(哈希桶)

拉链法 这里我们要是用string&#xff08;string来做key&#xff09;来取模&#xff0c;再增加一个模板参数&#xff0c;配一个取模的仿函数即可

leetcode 133. 克隆图

leetcode 133. 克隆图 给你无向 连通 图中一个节点的引用&#xff0c;请你返回该图的 深拷贝&#xff08;克隆&#xff09;。 图中的每个节点都包含它的值 val&#xff08;int&#xff09; 和其邻居的列表&#xff08;list[Node]&#xff09;。 class Node { public int val;…

Flex布局是什么?

一、Flex布局是什么&#xff1f; Flex是Flexible Box的缩写&#xff0c;意为”弹性布局”&#xff0c;用来为盒状模型提供最大的灵活性。 注意&#xff0c;设为Flex布局以后&#xff0c;子元素的float、clear和vertical-align属性将失效。 二、基本概念 采用Flex布局的元素…

基于JavaWeb(Servlet+jsp)的个人通讯录管理系统(含实验报告)

基于JavaWeb&#xff08;Servletjsp&#xff09;的个人通讯录管理系统&#xff08;含实验报告&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 本项目为简单的基于Javaweb实现的个人通讯录管理系统&#xff0c;实现主要功能为用户注册登录、首页展示、用户联…

C++——如何正确的使用STL中的vector?

什么是vector&#xff1f; 在STL&#xff08;标准模板库&#xff09;中&#xff0c;vector是一种动态数组容器&#xff0c;可根据需要自动增长或缩小。它可以存储任意类型的元素&#xff0c;并且支持快速的随机访问。 vector是表示可变大小数组的序列容器vector采用的是连续的…