当前位置: 首页 > news >正文

GC垃圾回收

Gc是语言提供的自动的内存管理机制,自动释放不需要的内存对象,让出存储器资源。
Go语言变革:
V1.5的三色并发标记法
V1.5的三色并发标记为什么需要STW
V1.5的三色标记为什么需要屏障机制(“强-弱”,三色不变式、插入屏障、删除屏障)
V1.8混合写屏障机制
V1.8混合写屏障机制的全场景分析

GoV1.3之前的标记-清除算法

  • 标记
  • 删除

标记清除算法的具体步骤

  1. 暂停程序业务逻辑,分类出可达和不可达对象,然后做上标记
    在这里插入图片描述

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

  3. 标记完之后,清除未被标记的对象

在mark和sweep算法在执行的时候,需要程序暂停! STW过程中,CPU不执行用户代码全部用于垃圾回收,影响很大急需优化。因此在执行第三步时,程序会暂停所有工作,一直等待所有回收执行完成。
在这里插入图片描述

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

标记-清除的缺点

  1. STW让程序暂停,程序出现卡顿
  2. 标记需要扫描整个heap(堆);
  3. 清除数据会产生heap(堆)碎片;
    V1.3版本之前在这里插入图片描述
    V1.3版本优化![[file-20250409203132341.png]]

上图主要是将STW步骤提前了一步,因为在sweep清除的时候,可以不需要STW停止,这些对象不会出现回收写冲突问题。
但是这个最大的问题是–STW会暂停整个程序。
因此出现三色并发标记法优化。

V1.5三色并发标记法

Golang中GC可以和其他用户goroutine并发运行但是需要一定时间STW,三色标记法就是通过三个阶段的标记来确定清除的对象都有哪些

三色并发标记法步骤

  1. 每次新创建的对象,默认的颜色都是标记为“白色”![[file-20250409203741372.png]]

上图所示,我们的程序可抵达的内存对象关系如左图所示,右边的标记表,是用来记录目前每个对象的标记颜色分类。程序其实是一些对象的根节点集合。将程序展开,会得到类似如下的表现形式。
![[file-20250409204030105.png]]

  1. 每次GC回收开始,会从根节点开始遍历所有对象,把遍历到的对象从白色集合放入灰色集合![[file-20250409204138423.png]]

这里的遍历是一次遍历,非递归形式,是从程序抽次可抵达的对象遍历一层,当前可抵达的对象是对象1和对象4,本次遍历结束,1和4就会被标记为灰色,灰色标记表就会多出两个对象。
3. 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合![[file-20250409204410415.png]]

这一次遍历只是扫描灰色对象,将灰色对象的第一层可遍历可抵达的对象有白色变为灰色如对象2 和对象7。而之前的灰色对象1和对象4会被标记为黑色,由灰色标记表移动到黑色标记表中。
4. 重复第三步,知道灰色中无任何对象![[file-20250409204648862.png]]![[file-20250409204654859.png]]

当我们全部的可达对象都遍历完后,灰色标记表将不再存在灰色对象,目前全部内存的数据只有两个颜色,黑色和白色。黑色对象是我们程序逻辑可达对象,是合法有用的数据不可删除,白色的对象全部不可达对象,目前程序不依赖即垃圾数据
5. 回收所有的白色标记表的对象,也就是回收垃圾![[file-20250409205129338.png]]

以上就是三色并发标记法,但是这里面可能会有很多并发流程均会被扫描,执行并发流程的内存可能相互依赖,为了在GC过程中保证数据的安全,我们在开始三色标记之前就会加上STW,在扫描确定黑白对象之后再放开STW。但是这样明显GC扫描的性能实在是太低了。

没有STW的三色标记法

我们把初始状态设置为已经经历了第一轮扫描,目前黑色的有对象1和对象4,灰色的有对象2和对象7,其它均为白色;其中对象2通过指针p指向对象3![[file-20250409211626153.png]]
如果三色标记过程不启动STW,那么在GC扫描过程中,任意的对象均可能发生读写。如图,在扫描对象2之前,已经标记为黑色的对象4,此时创建指针q,并且指向白色的对象3![[file-20250410143840987.png]]
同时灰色对象2将指针p移除,那么白色的对象实则就是被挂在了已经扫描完成的黑色的对象4下![[file-20250410143935581.png]]
然后我们将所有灰色对象标记为黑色,那么对象2 和对象7就被标记成了黑色![[file-20250410144010276.png]]
然后将白色对象当做垃圾进行回收![[file-20250410144059427.png]]
这就出现了对象4合法引用的对象3被GC误杀回收掉了

屏障机制

三色标记不希望发生的情况

没有STW会出现的情况
条件1.一个白色对象被黑色对象引用
条件2.灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色)
满足以上两种条件就会出现对象丢失现象。

(1). “强-弱”三色不变式

  • 强三色不变式–不允许黑色对象引用白色对象![[file-20250410144953671.png]]
  • 弱三色不变式
    黑色对象只能引用存在被其他灰色对象引用或者可达它的链路上游有灰色对象的白色对象![[file-20250410145123876.png]]
    GC遵循上述两种方式,演变出两种屏障方式

插入屏障

在A引用对象B时,B对象内标记为灰色。满足强三色不变式
写屏障操作在栈空间对象操作中不使用,因为栈空间调用更加频繁要求速度快,如果引入写屏障机制会增加额外的开销。
因此插入屏障仅仅用于堆空间对象的操作中。
![[file-20250410153155561.png]]
![[file-20250410153207332.png]]
![[file-20250410153215732.png]]
![[file-20250410153220951.png]]
![[file-20250410153230197.png]]
![[file-20250410153235569.png]]
如果栈不添加插入屏障,三色标记后可能存在白色对象被引用的情况(GC不清理栈空间),(这个栈上的白色对象可能会逃逸到堆上,导致被清除) 所以要对栈重新进行三色标记扫描,此时需要启用STW直到栈空间的三色标记结束。
![[file-20250410153416640.png]]
![[file-20250410153424496.png]]
![[file-20250410153429929.png]]
清除所有白色节点
![[file-20250410153450096.png]]

删除屏障

具体操作:被删除的对象(即不再被其他对象引用),如果自身为灰色或者白色,那么被标记为灰色。为了确保其可达性在后续标记阶段被重新检查,避免因并发操作导致的错误回收。
满足:弱三色不变式(保证灰色对象到白色对象的路径不会断)
![[file-20250410161302131.png]]
![[file-20250410161325683.png]]
![[file-20250410161331815.png]]
![[file-20250410161336407.png]]
![[file-20250410161342178.png]]
![[file-20250410161346858.png]]
![[file-20250410161351547.png]]

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

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

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

V1.8混合写屏障机制

混合写屏障规则

具体操作

  1. GC开始直接将栈上的对象标记为黑色(只扫描这一次
  2. GC期间,任何在栈上创建的新对象,均为黑色(防止内存逃逸,栈上的白色对象逃逸到堆上被GC回收)
  3. 被删除的对象标记为灰色
  4. 被添加的对象标记为灰色
    满足:变形的弱三色不变式
    屏障技术不在栈上应用,保证栈的运行效率

混合写屏障的具体场景分析

注意混合写屏障是Gc的一种屏障机制,所以只是当程序执行GC的时候,才会触发这种机制。

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

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

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

![[file-20250410163935971.png]]

![[file-20250410163941116.png]]

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

![[file-20250410164157301.png]]
![[file-20250410164212843.png]]
![[file-20250410164235142.png]]

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

![[file-20250410164317614.png]]
![[file-20250410164339775.png]]
![[file-20250410164350707.png]]

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

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

辅助GC(Mutator Assist)

为了防止内存分配过快,在GC执行过程中,如果goroutine需要分配内存,那么这个goroutine会参与一部分GC的工作,即帮助GC做一部分工作,这个机制叫作Mutator Assist。

垃圾回收触发时机

内存分配量达到阈值触发GC

默认情况下,最长2分钟触发一次GC,这个间隔在src/runtime/proc.go:forcegcperiod变量中被声明:

// forcegcperiod is the maximum time in nanoseconds between garbage
// collections. If we go this long without a garbage collection, one
// is forced to run.
//
// This is a variable for testing purposes. It normally doesn't change.
var forcegcperiod int64 = 2 * 60 * 1e9

手动触发

程序代码中也可以使用runtime.GC()来手动触发GC。这主要用于GC性能测试和统计。

GC性能优化

GC性能与对象数量负相关,对象越多GC性能越差,对程序影响越大。

所以GC性能优化的思路之一就是减少对象分配个数,比如对象复用或使用大对象组合多个小对象等等。

另外,由于内存逃逸现象,有些隐式的内存分配也会产生,也有可能成为GC的负担。

相关文章:

  • 「OC」源码学习—— 消息发送、动态方法解析和消息转发
  • 【RP2350】香瓜树莓派RP2350之USB虚拟串口
  • 操作系统 : 线程同步与互斥
  • 深入浅出之STL源码分析7_模版实例化与全特化
  • 「银河通用」创始人王鹤:人形机器人跳舞是预先编程,马拉松是遥控操作!
  • 【PostgreSQL系列】PostgreSQL性能优化
  • java加强 -Collection集合
  • HTML5表格语法格式详解
  • [Java实战]Spring Boot 中Starter机制与自定义Starter实战(九)
  • 端口号被占用怎么解决
  • 深入解析 Vision Transformer (ViT) 与其在计算机视觉中的应用
  • 计算机网络|| 常用网络命令的作用及工作原理
  • 罗技无线鼠标的配对方法
  • windows CUDA与CUDNN安装教程
  • 判断两台设备是否在同一局域网内的具体方法
  • MyBatis快速入门——实操
  • 基于VSCode + PlatformIO平台的ESP8266的DS1302实时时钟
  • 【安装配置教程】ubuntu安装配置Kodbox
  • POSIX信号量
  • Kubernetes生产实战(二十七):精准追踪Pod数据存储位置
  • 日月谭天丨这轮中美关税会谈让台湾社会看清了什么?
  • 经济日报整版聚焦:上海构建法治化营商环境,交出高分答卷
  • 专访|茸主:杀回UFC,只为给自己一个交代
  • 西藏日喀则市拉孜县发生5.5级地震,震源深度10千米
  • 乘联分会:上半年车市价格竞争温和,下半年价格战或再开启
  • 巴基斯坦称对印度发起军事行动