Golang的垃圾回收机制

什么是垃圾回收?

传统的系统级编程语言(主要指C/C++)中,程序员必须对内存小心的进行管理操作,控制内存的申请及释放。稍有不慎,就可能产生内存泄露问题,这种问题不易发现并且难以定位,一直成为困扰开发者的噩梦。为了解决这个问题,后来开发出来的几乎所有新语言(java,python,php等等)都引入了语言层面的自动内存管理 – 也就是语言的使用者只用关注内存的申请而不必关心内存的释放,内存释放由虚拟机(virtual machine)或运行时(runtime)来自动进行管理。而这种对不再使用的内存资源进行自动回收的行为就被称为垃圾回收(Garbage Collection),简称GC

常见的垃圾回收方法

引用计数(reference counting)

对每个对象维护一个引用计数,当引用该对象的对象被销毁或更新时被引用对象的引用计数自动减一,当被引用对象被创建或被赋值给其他对象时引用计数自动加一。当引用计数为0时则立即回收对象。

  1. 优点:实现简单,并且内存的回收很及时。这种算法在内存比较紧张和实时性比较高的系统中使用的比较广泛。
  2. 缺点:频繁更新引用计数降低了性能。循环引用问题,当对象间发生循环引用时引用链中的对象都无法得到释放。

标记-清除(mark and sweep)

这个算法分为两步,标记和清除。
标记:从程序的根节点开始, 递归地遍历所有对象,将能遍历到的对象打上标记。
清除:将所有未标记的对象当作垃圾销毁。

缺点:是人们常常说的 STW 问题(Stop The World)。因为算法在标记时必须暂停整个程序,否则其他线程的代码可能会改变对象状态,从而可能把不应该回收的对象当做垃圾收集掉。当程序中的对象逐渐增多时,递归遍历整个对象树会消耗很多的时间。在大型程序中这个时间可能会是毫秒级别的,等待时间太久了不能忍。

分代收集(generation)

分代收集是传统 Mark-Sweep 的一个改进。这个算法是基于一个经验:绝大多数对象的生命周期都很短。所以按照对象的生命周期长短来进行分代。

一般 GC 都会分三代:
在Java 中称之为新生代(Young Generation)、年老代(Tenured Generation)和永久代(Permanent Generation)。
在 .NET 中称之为第0代、第1代和第2代。

工作原理如下:

  1. 新对象放入第0代;
  2. 当内存用量超过一个较小的阈值时,触发0代收集;
  3. 将第0代幸存的对象(未被收集)放入第1代;
  4. 只有当内存用量超过一个较高的阈值时,才会触发 1 代收集;
  5. 2代同理。

因为 0 代中的对象十分少,所以每次收集时遍历都会非常快(比 1 代收集快几个数量级)。只有内存消耗过于大的时候才会触发较慢的 1 代和 2 代收集。

三色标记法

三色标记法是传统 Mark-Sweep 的一个改进,它是一个并发的 GC 算法。
工作原理如下:

  1. 首先创建三个集合:白、灰、黑。
  2. 将所有对象放入白色集合中。
  3. 然后从根节点开始遍历所有对象(注意这里并不递归遍历),把遍历到的对象从白色集合放入灰色集合。
  4. 之后遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合。
  5. 重复 4 直到灰色中无任何对象。
  6. 通过write-barrier检测对象有变化,重复以上操作。
  7. 收集所有白色对象(垃圾)。

这个算法可以实现 “on-the-fly”,也就是在程序执行的同时进行收集,并不需要暂停整个程序。
但是也会有一个缺陷,可能程序中的垃圾产生的速度会大于垃圾收集的速度,这样会导致程序中的垃圾越来越多无法被收集掉。

write-barrier(写屏障):对于和用户程序并发运行的垃圾回收算法,用户程序会一直修改内存,所以需要记录下来。

Go目前使用的就是三色标记算法。go 1.5 在源码中的解释是非分代的、非移动的、并发的、三色的标记清除垃圾收集器

Go的GC何时触发

我们对后台服务进行压力测试时发现,我们模拟大量的用户请求访问后台服务,这时各服务模块能观察到明显的内存占用上升。但是当停止压测时,内存占用并未发生明显的下降。花了很长时间定位问题,使用gprof等各种方法,依然没有发现原因。最后发现原来这时正常的…主要的原因有两个:

  1. Go的垃圾回收有个触发阈值,这个阈值会随着每次内存使用变大而逐渐增大(如初始阈值是10MB则下一次就是20MB,再下一次就成为了40MB…),如果长时间没有触发GC,Go会主动触发一次(2min)。高峰时内存使用量上去后,除非持续申请内存,靠阈值触发GC已经基本不可能,而是要等最多2min主动开始才能触发GC。
  2. Go语言在向系统交还内存时只是告诉系统这些内存不需要使用了,可以回收;同时操作系统会采取“拖延症”策略,并不是立即回收,而是等到系统内存紧张时才会开始回收。

Golang在GC的时候会发生Stop the world,整个程序会暂停,然后去标记整个内存里面可以被回收的变量,标记完之后恢复程序执行,最后异步得去回收内存。一般这个过程会达到20ms。标记可回收变量的时间取决于临时变量的个数。临时变量数量越多,扫描时间会越长

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值