GC垃圾回收

GC--Go语言垃圾回收机制

Go V1.3之前的标记-清除:

  1. 暂停业务逻辑,找到不可达的对象,和可达对象
  2. 开始标记,程序找出它所有可达的对象,并做上标记
  3. 标记完了之后,然后开始清除未标记的对象。
  4. 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束

标记-清除的缺点:

  1. STW(stop the world):让程序暂停,程序出现卡顿
  2. 标记需要扫描整个heap
  3. 清除数据会产生heap(就是"堆",这里简单理解为内存)碎片,内存中零星散落的未被利用的碎片内存。

Go V1.5 三色标记法

1.把新创建的对象,默认的颜色都标记为"白色"

  1. 每次GC回收开始,然后从根节点开始遍历所有对象,把遍历到的对象从白色集合放入"灰色"集合

3 .遍历灰色集合,将灰色对象引用的对象从白色集合放入到灰色集合,之后将此灰色对象放入到黑色集合

  1. 重复第三步,直到灰色中无任何对象
  1. 回收所有的白色标记的对象,也就是回收垃圾

三色标记法在不采用STW保护时会出现:

  1. 一个白色对象被黑色对象引用
  2. 灰色对象与它之间的可达关系的白色对象遭到破坏

这两种情况同时满足,会出现对象丢失

解决方案:

  1. 强三色不变式:强制性的不允许黑色对象引用白色对象(破坏1)
  2. 弱三色不变式:黑色对象可以引用白色对象,白色对象存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象(破坏2)

屏障:

  1. 插入屏障:在A对象引用B对象的时候,B对象被标记为灰色(满足强三色不变式,黑色引用的白色对象会被强制转换为灰色)。只有堆上的对象触发插入屏障,栈上的对象不触发插入屏障。在准备回收白色前,重新遍历扫描一次栈空间。此时加STW暂停保护栈,防止外界干扰。

不足:在一轮GC的结尾需要STW来从新扫描栈空间

  1. 删除屏障:当一个对象准备删除对某个对象的应用的时候,如果被删除的对象是灰色或者白色则一律置为灰色(满足弱三色不变式)

不足:回收进度较低,有些被删除的对象在本轮GC中得到保护,在下论GC中才会被删除。

Go V1.8的三色标记法+混合写屏障机制

具体操作:

  1. GC开始将栈上的可达对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW)
  2. GC期间,任何在栈上创建的新对象,均为黑色
  3. 堆上被删除对象标记为灰色
  4. 堆上被添加的对象标记为灰色
    满足:变形的弱三色不变式(结合了插入、删除写屏障的优点)

对于堆上的对象,采用三色标记法+写屏障保护

GC垃圾收集的多个阶段:

  1. 标记准备阶段;

启动后台标记任务

暂停程序(STW),所有的处理器在这时会进入安全点(Safe point);

如果当前垃圾收集循环是强制触发的,我们还需要处理还未被清理的内存管理单元;

将根对象入队

开启写屏障

  1. 标记阶段

恢复用户协程

使用三色标记法开始标记,此时用户协程和标记协程并发执行

  1. 标记终止阶段

暂停用户协程

计算下一次触发GC时需要达到的堆目标

唤醒后台清扫协程

  1. 清理阶段

关闭写屏障

恢复用户协程

异步清理回收

什么是根对象?

根对象(root object)是指那些能够从全局可达的地方访问到的对象。垃圾回收器会从根对象开始,通过遍历根对象的引用关系,逐步追踪并标记所有可达的对象。任何未被标记的对象都会被认为是垃圾,最终被回收释放。 (简而言之:就是正在运行的所有程序的使用的空间里面的变量和全局变量)

  1. 全局变量:全局变量可以被程序中的任何位置引用到,因此是根对象。
  2. 当前正在执行的函数的局部变量:当一个函数正在执行时,其局部变量可以被当前函数中的代码访问到,因此也是根对象。
  3. 当前正在执行的 goroutine 的栈中的变量:goroutine 是 Go语言并发编程中的轻量级线程,每个 goroutine 都有一块独立的栈空间,其中的变量可以被当前 goroutine 访问到,也是根对象。
  4. 其他和运行时系统相关的数据结构和变量。

GC的触发条件

  1. 主动触发(手动触发),通过调用 runtime.GC 来触发GC,此调用阻塞式地等待当前GC运行完毕。
  2. 被动触发,分为两种方式:

2.1. 使用步调(Pacing)算法,其核心思想是控制内存增长的比例,每次内存分配时检查当前内存分配量是否已达到阈值(环境变量GOGC):默认100%,即当内存扩大一倍时启用GC。

2.2. 使用系统监控,当超过两分钟没有产生任何GC时,强制触发 GC。

GC调优

  1. 控制内存分配的速度,限制Goroutine的数量,提高赋值器mutator的CPU利用率(降低GC的CPU利用率)
  2. 少量使用+连接string
  3. slice提前分配足够的内存来降低扩容带来的拷贝
  4. 避免map key对象过多,导致扫描时间增加
  5. 变量复用,减少对象分配,例如使用sync.Pool来复用需要频繁创建临时对象、使用全局变量等
  6. 增大GOGC的值,降低GC的运行频率
相关推荐
寻星探路12 小时前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
lly20240613 小时前
Bootstrap 警告框
开发语言
2601_9491465314 小时前
C语言语音通知接口接入教程:如何使用C语言直接调用语音预警API
c语言·开发语言
曹牧14 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
KYGALYX14 小时前
服务异步通信
开发语言·后端·微服务·ruby
zmzb010314 小时前
C++课后习题训练记录Day98
开发语言·c++
爬山算法15 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty72515 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎15 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven