Golang 垃圾回收机制详解

一、垃圾回收概念

有些编程语言(C、C++)需要手动释放那些不再需要的、分配在堆上的数据,这叫做“手动垃圾回收”,但是如果数据释放过早,后续对数据的访问就会出错,因为被释放的内存可能已经被清空或者重新分配;如果忘记释放,数据会一直占用内存,出现“内存泄漏”,所以越来越多的编程语言支持“自动垃圾回收”。
由运行时识别不再有用的数据并释放他们占用的内存,内存何时被释放,被释放的内存何时处理都不需要我们考虑

二、常用的垃圾回收算法

引用计数

引用计数指的是一个数据对象被引用的次数,程序执行的过程中,会更新对象的引用计数,当引用计数更新为0时,就表示这个对象不再有用,可以回收它占用的内存了,所以在引用计数法垃圾识别的任务已经分摊到每一次数据对象的操作中
缺点:
1.高频率的更新引用计数也会造成不小的开销
2.若是A引用了B,B也引用了A,形成循环引用,当A和B的引用计数更新到只剩彼此的相互引用时,引用计数便无法更新到0,也就不能回收对应的内存了

标记——清除算法

程序中用的到的数据一定是从栈、数据段这些根节点追踪得到的数据,虽然能够追踪的到但不代表后续一定会用得到,但是根节点追踪不到的数据就一定不会被用到,也就一定是垃圾。
要识别存活对象,可以把栈、数据段上的数据对象作为root,基于他们进一步追踪,将能追踪到的数据都进行标记,剩下的追踪不到的就是垃圾

算法分两个部分:标记(mark)和清扫(sweep)。标记阶段表明所有的存活单元,清扫阶段将垃圾单元回收。
(1)标记阶段
在此阶段,垃圾回收器会从应用程序的根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。
(2)清理阶段
在此阶段中,垃圾回收器,会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作
优点:无循环引用问题;不用维护计数开销
缺点:需要STW,应用程序的执行会暂停;非渐进式需遍历整个堆空间,开销大

三色标记算法

  • 白色:还没有搜索过的对象(标记结束后,白色对象会被当成垃圾对象)
  • 灰色:正在搜索的对象
  • 黑色:搜索完成的对象(不会当成垃圾对象,不会被GC)

假设现在有白、灰、黑三个集合(表示当前对象的颜色),其遍历访问过程为:

  1. 初始时,所有对象都在【白色集合】中;
  2. 将 GC Roots 直接引用到的对象挪到 【灰色集合】中;
  3. 从灰色集合中获取对象
    3.1. 将本对象引用到的其他对象全部挪到 【灰色集合】中;
    3.2. 将本对象挪到【黑色集合】里面。
  4. 重复步骤3,直至【灰色集合】为空时结束。
  5. 结束后,仍在【白色集合】的对象即为 GC Roots 不可达,可以进行回收。

注:如果标记结束后对象仍为白色,意味着已经“找不到”该对象在哪了,不可能会再被重新引用。

标记——整理算法

标记出所有可达对象,然后将可达对象移动到空间的另外一段,最后清理掉边界以外的内存。
优点:
避免了内存碎片化的问题
适合老年代算法:老年代对象存活率高的情况下,标记整理算法由于不需要复制对象,效率更高
缺点:整理的过程复杂;需要多长遍历内存,导致STW时间比标记清除算法高

分代收集算法

弱分代假说:大部分对象都在年轻时死亡

对于生命周期短的新生代区域,每次回收仅需要考虑如何保留少量存活对象,因此可以采用标记——复制法完成GC
对于生命周期长的老年代区域,可以通过减少gc的频率来提高

效率,同时由于对象存活率高没有额外的空间用于复制,因此一般可以使用标记清除法或标记整理法
这样划分,堆就分成了Young和Old两个分区,因此GC也分为新生代GC和老年代GC

对象的分配策略:
对象优先在新生代上Eden区域分配
大部分对象直接进入老年代
新生代中周期较长的对象在s0或s1区每经过一次新生代Gc,就增加一岁,增加到一定阈值的时候,就进入老年代区域

三、屏障技术

三色标记清晰的描绘了垃圾回收中把存活对象误判为垃圾的情况
1.不出现黑色对象对白色对象的引用(强三色不变式),可以把白色指针着为灰色,也可以把写入的黑色对象退回灰色,这被称为是插入写屏障
2.允许出现黑色对象对白色对象的引用,但是可以保证通过灰色对象可以抵达白色对象(弱三色不变式),提醒我们关注对到白色路径的破坏行为,防止此破坏行为的出现,我们可以把白色对象着色为灰色,这被称为是删除写屏障
实现强/弱三色不变式的通常做法是建立读写屏障

要解决三色标记法的并发性问题,有两种思路:
读屏障技术:非移动式垃圾回收器中,天然的不需要读屏障;在复制式回收器会移动数据避免碎片化,此时读数据也不那么安全,需要读屏障技术,确保用户程序不会访问已经存在副本的陈旧对象
写屏障技术:在写操作中插入指令,目的是把数据对象的修改通知到垃圾回收期,因而写屏障通常都要有一个记录集,记录集采用顺序存储,使用哈希表,记录精确到被修改的对象只记录其所在页

四、运行模式

1.增量式垃圾回收

可以分摊GC时间,避免程序长时间暂停
存在的问题:
1.分摊GC时间,避免程序长时间暂停
2.内存屏障技术,需要额外时间开销,并且由于内存屏障技术的保守性,一些垃圾对象不会被回收,会增加一轮gc的总时长

2.并行垃圾回收

原本需要一个线程全权负责的任务,现在需要分几分交给多个线程
实现较好的负载均衡,会增加线程间的同步开销

3.并发垃圾回收

用户程序与垃圾回收程序并发执行,在多核场景下,就会出现用户程序和垃圾回收程序并行执行的情况
一定程度上利用多核计算机的优势减少了对用户程序的干扰,写屏障的额外开销和保守性问题仍然存在,这是不可避免的

关于gc触发时间:

  • 堆内存到达一定阈值
  • 距离上次gc超过一定阈值
  • 如果当前没有启动gc,则开始新一轮gc

关于gc调优:

  • 尽量将小对象组合成大对象
  • 尽量使用小数据类型
  • 大量string拼接时使用string.join,而不是+号(go中string只读,每一个针对string的操作都会创建一个新的string)

参考

Golang-垃圾回收原理解析
幼麟实验室

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值