Golang--垃圾回收

常见垃圾回收

垃圾回收介绍

程序运行过程中,函数的局部变量、参数和返回值都在栈中。

在函数返回后,该函数调用栈会被销毁,一些不能在编译阶段就确定大小的对象、或生命周期超出当前所在函数的对象就不适合分配在栈上,需要分配在堆上。

分配在栈上的数据会随着栈的销毁而释放自身占用的内存,但是堆上的数据需要程序主动释放。

手动进行垃圾回收:如,CPP要求程序员自己控制垃圾回收,但是数据释放早会导致之后的访问出错(悬挂指针问题),忘记释放则会数据一直占用内存导致内存泄漏。故多种编程语言支持自动垃圾回收。

自动垃圾回收:程序员不再需要关心内存何时被释放,被释放的内存如何处理,都交给语言自带的垃圾回收功能处理。

常见垃圾回收算法

引用计数式

为每一个对象维护一个引用计数,每当引用该对象的对象被销毁,引用计数就减1,引用计数器为0则回收该对象。

优点:对象及时被回收,不会出现内存耗尽或者达到某个阈值才回收

缺点:不能很好地处理循环引用,实时维护引用计数也需要代价

追踪式回收

  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">标记清除(Mark-Sweep)</font>

从栈、数据段上的根变量开始遍历所有引用的对象,能够通过遍历访问到的对象标记为"被引用"。没有被标记的内存进行回收。

三色标记法(类似BFS遍历):

  1. 最开始所有的对象都是白色
  2. 从根扫描所有可达的对象,标记为灰色,放入待处理队列
  3. 从队列取出灰色对象,其引用的对象都标记为灰色继续放入队列,自身则标记为黑色
  4. 不断重复3操作,直到队列为空。此时所有的白色对象即为垃圾,可以回收。

三色标记法可以让标记过程过程和用户程序并发的进行。

  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">标记-压缩(Mark-Compact)</font>

标记清除比较容易出现内存碎片问题,故可以完成标记工作后,移动非垃圾数据,让其紧凑的放在内存中。

但是其多次扫描和移动开销也很大。

  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">复制回收</font>

将堆内存分为From、To两部分,程序执行时使用From空间,垃圾回收时扫描From空间将能追踪到的数据给复制到To空间,回收全部的From空间,From和To交换。

该方式不会产生内存碎片问题,但是会让堆内存只有一半在使用,故可以只在一部分堆内存中使用该方法。

  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">分代回收</font>

新创建的对象称为新生代对象,多次垃圾回收仍然存活的对象称为老年代对象。

弱分代假说:大部分对象都会在年轻时死亡。也就是说新生代对象称为垃圾的概率高于老年代对象。

将数据分为新生代和老年代,可以降低老年代的回收频率,不用每次处理所有数据;新生代和老年代也可以使用不用的垃圾回收策略。

并发/并行执行垃圾回收

每次进行垃圾回收都需要暂停用户程序,即STW(stop the world)。

但是用户难以接收一次性暂停长段时间进行完整的GC工作,故可以将GC分为多次完成,使得用户程序和GC交替进行,缩短每一次暂停的时间。

或是多核并行执行用户程序和GC,则无需对用户程序进行STW。

但是并发/并行执行又会产生新的问题:

以三色标记法为例,若在GC时将A标记为黑色,即扫描完成,不会再进入待处理队列;下一次的用户程序中又让A引用了一个没有任何别的引用的白色对象B;再进行GC时由于黑色对象A不会再被扫描,其引用的白色对象B就会被当为垃圾被清理,导致A找不到B。

简单来说就是,白色被挂在黑色上的同时灰色丢失了对该白色的引用

解决办法:

  • 强三色不变式:不允许黑色对象对白色对象的引用。
  • 弱三色不变式:黑色对象对白色对象的引用,但必须保证有灰色对象也引用该白色对象

读写屏障

为了防止并行/并发GC时出现误删除的情况,引入了屏障机制。

屏障机制类似一种额外的判断机制。

写屏障

  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">插入写屏障</font>

当一个对象被引用时触发该机制。

在A对象引用B对象时,B对象就被标记为灰色;即B挂在A的下游,B必须被标记为灰色,满足强三色不变式。

但是插入写屏障仅仅在堆上使用,而不在栈上使用。

为了保证栈上被引用的白色对象不会被清除,在回收白色对象之前,会进行STW并对栈空间重新进行一次三色标记。

插入写屏障的不足也在于此,GC的最后需要进行一次短时间的STW来重新标记一遍栈。

  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">删除写屏障</font>

当对象被删除引用时触发该机制。

被删除引用的对象,会被标记为灰色;满足弱三色不变式。

但是由于被删除的对象变为了灰色,当次GC不会将其清理,需要等到下一次GC才能清理它。

故,删除写屏障的不足在于,回收精度低,删除的对象不能立即清理。

  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">读屏障</font>

标记-清除这类GC不会进行移动数据,天然不需要读屏障。

复制回收这种会移动数据来避免碎片化的GC,当GC和用户程序交替执行,会出现问题,如:

GC将A从From被复制到To;用户程序B引用了A,但是此时B中包含的A指针指向的是From中的A;

GC将B复制进To并清除From,则B中的A指针便找不到引用的A数据了。

此时需要读屏障来保证用户程序不会访问到在From中已经复制到To中的对象。

可以在检测到A已经复制到To中时,就让用户程序去读取To中A复制,而不要读取From中的A。

相关推荐
Tbisnic22 分钟前
BGE-M3 算法详解:从模型架构到三种检索方式的数学原理
算法·自然语言处理·大模型·bert·transformer·注意力机制
聪明蛋子哟30 分钟前
Stagehand v3多语言SDK:Python/Go/Rust/Java下的浏览器自动化统一方案
python·golang·rust
最强小杰39 分钟前
gpt-5.6-sol 频繁报 503 怎么办?区分容量熔断和限速 429 的排查方法 + 可复用 retry wrapper
java·人工智能·gpt·ai
Brilliantwxx44 分钟前
【算法从零到千】【55-58】哈希位图+常见数学运算 接口
算法
唐青枫1 小时前
一个值多种形态:Zig union、Tagged Union 与内存布局实战
后端
今天AI了吗1 小时前
Python 基础语法(一):常量、变量、输入输出与运算符
开发语言·数据库·人工智能·python·sql·深度学习·机器学习
卷无止境1 小时前
Windows 上丝滑开发 Python,并稳定构建 Docker 镜像
后端·python·docker
Nturmoils1 小时前
备份完不算完,先还原到临时库验一遍
后端
空堂与归2 小时前
用户分群找不到规律?用K-Means聚类算法自动发现数据模式
算法·机器学习·kmeans·聚类
TELL5212 小时前
selenium webdriver 第二次初始化的异常
开发语言·python