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。

相关推荐
2501_945423542 小时前
模板编程中的SFINAE技巧
开发语言·c++·算法
填满你的记忆2 小时前
RAG 架构在实际项目中的应用(从原理到落地)
java·ai·架构
承渊政道2 小时前
【优选算法】(实战感悟二分查找算法的思想原理)
c++·笔记·学习·算法·leetcode·visual studio code
☆5662 小时前
C++中的策略模式应用
开发语言·c++·算法
Dylan~~~2 小时前
Go语言Web框架选型指南:从入门到精通
开发语言·前端·golang
Densen20142 小时前
企业H5站点升级PWA (二)
java·后端·spring
2401_884563242 小时前
C++中的原型模式变体
开发语言·c++·算法
Aaa111114432 小时前
限流算法 限流算法
java·开发语言
m0_569881472 小时前
使用Python自动收发邮件
jvm·数据库·python