Go --- Go语言垃圾处理

概念

  • 垃圾回收(GC-Garbage Collection)
  • 暂停程序业务逻辑SWT(stop the world)
  • 程序根节点:程序中被直接或间接引用的对象集合,能通过他们找出所有可以被访问到的对象,所以Go程序的根节点通常包括以下几个对象
    • 程序的全局变量和静态变量
    • 程序的调用栈中的变量
    • 当前执行的goroutine

在这里插入图片描述

知识点

创建的值在物理内存中,但是物理内存终归是有限的所以需要有垃圾回收(GC)机制。Go语言标准工具链为每一个应用程序都提供了一个runtime库,这个runtime库包含一个垃圾收集器。

为什么要有垃圾回收机制。

go里面的值存在了哪里?

存储在局部变量里的非指针的Go值通常不交由Go GC 管理,Go 将安排分配与创建它的词法范围相关的内存,这种内存分配方式往往比Go GC管理来的更有效,因为Go编译器可以知道预先知道何时释放内存和清除内存。这种分配方式被称为“栈分配”(stack allocation),因为变量存放在goroutine栈空间上。

Go编译器无法确定其生命周期,无法以这种方式分配内存的Go值被称为逃逸到堆(escape to the heap),可以将堆看成是一个内存分配的大包裹,当Go值需要分配内存时,就往里边装。在堆上进行内存分配的行为为动态内存分配,这种类型的内存分配不会假设Go值合适使用和何时释放。如此需要清理这些内存就需要用到Go GC——专门识别和清理动态内存分配的系统了。

虽然GC并不管理栈上的内存,但是扔然需要扫描调用栈中的变量,以确保不会回收被其他对象引用的变量

为什么Go值需要逃逸到堆,一个可能的原因是它的大小是动态决定的,如切片(slice)。需要注意的是,这个逃逸是传递的,如果一个Go值由一个已经逃逸到堆的Go值引用,那这个也肯定是逃逸的。

Go值是否逃逸需要依据上下文和Go编译器的逃逸算法,这个算法非常复杂,而且会随着版本更迭。如果想要更多的了解可以去看 eliminating heap allocations。

在这里插入图片描述

追踪垃圾回收

垃圾回收也许指的是不同自动回收内存的方法,如引用计数。但本文档介绍的是垃圾回收指的是跟踪垃圾回收,他通过跟随指针来识别正在使用的、活动的对象。

首先让我们先明确一下定义:

对象——对象是一块动态分配的内存,包含一个或多个 Go 值

指针——引用对象内任何值的内存地址。这自然包括 *T 形式的 Go 值,但也包括部分内置 Go 值。如字符串、切片、通道、映射和接口值都包含 GC 必须跟踪的内存地址。

对象和指向其他对象的指针一起形成对象图,为了识别实时内存,GC 从程序的根部开始遍历对象图,这些指针标识程序肯定正在使用的对象。根的两个例子是局部变量和全局变量。遍历对象图的过程称为扫描

这个基本的算法是所有追踪GC所共有的,不同追踪GC的不同之处在于一旦发现内存处于活跃状态,他们会采取什么措施。就Go GC而言,他采用的是标记-清除技术,这意味着为了跟踪其进度,GC会将其遇到得值标记为活跃状态。等待跟踪完毕后,GC会遍历堆中的所有内存,并使所有未标记的为可供分配的内存。这个过程称为清除

你可能熟悉的一种替代技术是将对象实际移动到内存的新部分并留下转发指针,该指针稍后用于更新所有应用程序的指针。我们将这种移动对象的GC称为移动GC; Go 有一个不动的 GC。

GC 周期

由于Go GC是标记-清除GC,因此它大致可以分为两部分运行:标记阶段和清除阶段,在进行标记时可能仍有未标记的对象被引用而处在活跃状态,所以清除行为必须是与标记行为完全分开的。此外,当没有GC相关工作要做时,GC也可能根本不活动。GC在所谓的GC周期中不断的轮流执行清除、关闭和标记这三个阶段。处于本文档的目的,请考虑从清除、关闭、然后标记开始的GC循环。

接下来的几节将重点关注建立对 GC 成本的直觉,以帮助用户根据自己的利益调整 GC 参数。

// 后面的区域以后再来探索吧。原文地址,需要小猫。

https://go.dev/doc/gc-guide

算法详情

不同版本的GC算法不同:

  • V1.3 标记-清除算法
  • V1.5 三色并发标记法
  • V1.8 混合屏障机制

标记清除算法

上面已经介绍了。

具体步骤

第一步:开始SWT,暂停程序业务逻辑, 分类出可达和不可达的对象,然后做上标记

第二步:开始标记,程序找出它所有可达的对象,并做上标记。

第三步: 标记完了之后,然后开始清除未标记的对象。

第四步:停止SWT,让程序继续跑。

缺点(不足):

标记算法清晰明了,但是有非常严重的问题。

  • 首先是STW,会让程序暂停,让运行出现卡顿
  • 在标记阶段会扫描整个堆。
  • 清除数据会产生堆碎片

虽然做了简单的优化(将清除阶段(第三步)和停止SWT(第四步)交换位置),但是优化后的算法仍然存在最大的问题就是使用了STW,算法会暂停整个程序。

三色并发标记法

所谓的三色标记法就是使用三种不同的状态来标记对象,根据对象状态的不同来确定需要清除的对象有那些。

具体步骤

第一步:新创建的对象,默认的颜色都是标记为“白色”。

第二步:开始从程序根节点开始遍历对象,找到可达对象,将可达对象从白染灰。注意:不是一下子将所有白色节点都遍历了,只是找到了根节点可达的白色节点。

第三步:遍历灰色节点找到可达对象,将可达对象染灰,然后该灰色节点染黑。

第四步:重复第三步,直到所有灰色节点均无可达对象,然后将所有灰色节点染黑。

第五步:回收所有白色节点(这些都是程序不可达的对象,是需要被清理的对象),省下的就只剩程序可达的黑色节点。

根据上述步骤,我们并没有发现他解决了需要引入STW而导致程序卡顿的问题。因为在遍历灰色节点寻找可达对象时为了保证数据安全还是会选择开始三色标记之前就加上STW。

为什么非要使用STW不可呢?

接下来我们分析一下以下情况。

一、当GC在三色标记的标记阶段,程序运行中出现黑色节点的对象引用了白色节点的对象,当然如果该白色节点恰好是可达对象,只是还没被GC扫描到哪还好说,但是如果该白色节点恰好是程序不可达对象,就会出现引用的对象在扫描结束后被清除的情况。一个被引用的对象就这样被清除了。

二、在标记过程中,一个灰色节点指向一个白色节点的引用指针被运行中的程序移除,同时一个黑色节点多了一个指向该白色节点的引用指针,等标记过后,发现该白色指针并没有被标记为黑色。一个被引用的对象就这样被清除了。

为了避免这两种情况出现,最简单的方法就是在标记之前启动STW,清除过后停止STW,但是会较大程度上影响程序的运行。在上面两种情况对应了两个条件。

一、不希望黑色节点后面直接连接白色节点

二、不希望灰色节点在黑色节点连接白色节点后被清除了与该白色节点的可达关系

因为我们并不希望使用STW,那么有什么方法能破坏这两个条件呢?

屏障机制

了解屏障机制之前需要先了解两个概念:

一、强三色不变式:不存在黑色节点引用到白色节点的指针。强制性不允许黑色节点指向白色节点。

二、弱三色不变式:所有被黑色节点引用的白色节点都处于灰色保护状态即被引用的白色节点终会在标记阶段变为可达对象。

接下来在来看两种屏障机制:

一、插入写屏障:

​ 具体操作:在A对象引用B对象的时候,B对象被标记为灰色。(将B挂在A下游,B必须被标记为灰色)

​ 满足强三色不变式:因为引入的对象都是黑色或灰色(白色节点变为灰色节点)。

黑色节点所在的内存空间有两个地方,一个是栈,一个是堆。栈的空间较小,但是要速度快,要负责函数的频繁调用,因此不在栈空间上使用插入屏障(不保障满足强三色不变式,黑色节点可以指向白色节点)。插入屏障只在堆空间上使用。

尽然这样就出现一个问题,在进行一次标记后,不能确保白色节点没有被引用。所以要对栈重新进行三色标记扫描, 但这次为了对象不丢失, 要对本次标记扫描启动STW暂停. 直到栈空间的三色标记结束。

虽然还是使用了STW,不过相比于标记整个程序的内存空间,只标记栈上的空间有了很明显的效率提升。

二、删除写屏障

具体操作:被删除的对象,如果自身为灰色或者白色,那么被标记为灰色。

满足弱三色不变式:保护灰色对象到白色对象的路径不会断。

这样也会有问题,比如一次GC周期不会删除GC启动时的可达但是过程中变为不可达的对象。

虽然引入了屏障机制,我们发现还是有不足之处:

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

接下来就看看GoV1.8是如何处理的

混合写屏障(hybrid write barrier)机制

规则:

1、GC扫描栈空间上的可达对象,并全部染为黑色。(之后不再进行第二次重复扫描,无需STW)。

2、GC期间,任何在栈上创建的新对象,均为黑色。

3、GC期间,被删除的对象标记为灰色。

4、GC期间,被添加的对象标记为灰色。

满足变相的弱三色不定式。

注意:混合写屏障也不在栈空间上使用,因为栈要求的是运行速率。

只有在GC执行时才会有屏障规则,普通条件下并不使用。

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

对比:

GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。

GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通

GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。

参考文章

语雀-Golang修养之路-刘丹冰Aceld
A Guide to the Go Garbage Collector
知乎-GC 机制中,所谓的“根节点”具体指的是什么?-orionnnn

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

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

相关文章

虚拟机Linux-openEuler硬盘空间扩容

虚拟机Linux-openEuler硬盘空间扩容 1、需求场景 我们在使用虚拟机时,可能会出现磁盘空间不够用导致各种bug出现的情况。 首先,我们要扩展虚拟机的可用磁盘空间。如图所示,我的原本硬盘大小为8G,我们扩展到30GB 2、打开虚拟机…

【git分支管理策略】如何高效的管理好代码版本

目录 1.分支管理策略 2.我用的分支管理策略 3.一些常见问题 1.分支管理策略 分支管理策略就是一些经过实践后总结出来的可靠的分支管理的办法,让分支之间能科学合理、高效的进行协作,帮助我们在整个开发流程中合理的管理好代码版本。 目前有两套Git…

【GameFramework框架内置模块】16、配置(Setting)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群:398291828 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录:…

2024年软件测试,“我“从初级到高级进阶,不再走弯路...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 现在2024年&#…

54、Qt/对话框、事件机制相关学习20240325

一、完善对话框,点击登录按钮,如果账号和密码匹配,则弹出信息对话框,给出提示”登录成功“,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到其他界面 如果账号和密码不匹配&#…

python(django)之单一接口管理功能后台开发

1、创建数据模型 在apitest/models.py下加入以下代码 class Apis(models.Model):Product models.ForeignKey(product.Product, on_deletemodels.CASCADE, nullTrue)# 关联产品IDapiname models.CharField(接口名称, max_length100)apiurl models.CharField(接口地址, max_…

服务运营|香港大学雷骁:收益管理中价格歧视的公平性

编者按: INFORMS George B. Dantzig Dissertation Award 用于表彰运筹学和管理科学领域中具有创新性和实用性的最佳毕业设计。香港大学助理教授雷骁题为“Revenue Management in Video Games and With Fairness” 是这一奖项2023年度的提名者之一。 这篇毕业设计重…

PMSM 永磁同步电机滑膜控制 SVPWM矢量控制 matlab simulink 仿真

仿真搭建平台: (1)该模型采用matlab/simulink 2016b版本搭建,使用matlab 2016b及以上版本打开最佳; (2)该模型已经提前转换了各个常用版本(最低为matlab2012b),防止出现提示版本过高的情况。 模型截图: 算…

网游版五子棋

五子棋游戏属于开房间类休闲游戏。可以非常方便实现分布式战斗服横向拓展,只要感觉服务器有压力,可以通过动态加战斗服服务器来实现。本文介绍一个基于jforgame组件开发的五子棋网络小游戏,支持分布式部署战斗服。 1.通信组件 浏览器&#…

Linux:进程概念认识

进程 基本概念 课本概念:程序的一个执行实例,正在执行的程序等 内核观点:担当分配系统资源( CPU 时间,内存)的实体。 描述进程 -PCB 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为…

34.基于SpringBoot + Vue实现的前后端分离-足球俱乐部管理系统(项目 + 论文)

项目介绍 系统包含用户、教练、管理员三个角色 用户:登录、注册、查看俱乐部公告信息、查看俱乐部赛事信息、个人中心等教练:登录、个人中心、用户管理、赛事管理、球员数据管理、训练计划管理、公告信息管理等管理员:登录、个人中心、教练…

Harmony OS 网络编程 实验指南

netcat简介 netcat 是什么? netcat是一个非常强大的网络实用工具,可以用它来调试TCP/UDP应用程序; netcat 如何安装? Linux上可以使用发行版的包管理器安装,例如Debian/Ubuntu上: sudo apt-get instal…

day07-缓存商品、购物车

1. 缓存菜品 1.1 问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。 结果: 系统响应慢、用户体验差 1.2 实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓…

Git_.gitignore文件相关知识

.gitignore 作用:指明不对哪些文件进行版本控制。 应当忽略哪些文件? 系统或软件自动生成的文件编译时产生的中间文件和结果文件运行时产生的日志文件,临时文件和缓存文件涉及身份,密码,口令,秘钥等敏感…

Java——基于CompletableFuture的流水线并行处理

CompletableFuture在JDK1.8提供了一种更加强大的异步编程的api。它实现了Future接口,也就是Future的功能特性CompletableFuture也有;除此之外,它也实现了CompletionStage接口,CompletionStage接口定义了任务编排的方法&#xff0c…

C语言结构体之位段

位段(节约内存),和王者段位联想记忆 位段是为了节约内存的。刚好和结构体相反。 那么什么是位段呢?我们现引入情景:我么如果要记录一个人是男是女,用数字0 1表示。我们发现只要一个bit内存就可以完成我们想…

Linux离线安装mysql,node,forever

PS:本文是基于centos7实现的,要求系统能够查看ifconfig和unzip解压命令, 实现无网络可安装运行 首先现在百度网盘的离线文件包****安装Xftp 和 Xshell 把机房压缩包传到 home目录下****解压unzip 包名.zip 获取IP先获取到 linux 主机的ip ifconfig Xftp 连接输入IP,然后按照…

FPGA----ZCU106的petalinux 2019.1使用USB传输数据

1、实际项目中需要用到开发板的串口进行数据交互,之前讲的几节只是启动了网口(如下链接)。因此,本次给大家带来的官方自带串口例程的使用方法,本文的vivado工程和下述连接一样,PL端什么配置都没有。 FPGA-…

ElasticSearch8 - 基本操作

前言 本文记录 ES 的一些基本操作,就是对官方文档的一些整理,按自己的习惯重新排版,凑合着看。官方的更详细,建议看官方的。 下文以 books 为索引名举例。 新增 添加单个文档 (没有索引会自动创建) POST books/_doc {"n…

flutter 弹窗之系列三

Overlay 部分源码 class Overlay extends StatefulWidget{...static OverlayState of(BuildContext context, {bool rootOverlay false,Widget debugRequiredFor,})... } // rootOverlay: // 值为false, 就近查找,找到树中最近的节点; // 如果为true, 则去找最顶…