19

浅析 Golang 垃圾回收机制

 3 years ago
source link: https://xie.infoq.cn/article/e4078a4d149f6bc80139159f1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

Google 搜索  Golang GC  排名靠前的文章都讲的不错,从设计到实现,从演进到源码,一应俱全。但是庞杂的信息会给人一种恐惧感,让人望而却步。本文尝试使用较为简单易懂的语言和图像,讲解 Golang 的垃圾回收机制。

垃圾回收算法

目前比较常见的垃圾回收算法有三种:

  1. 引用计数:为每个对象维护一个引用计数,当引用该对象的对象销毁时,引用计数 -1,当对象引用计数为 0 时回收该对象。

  • 代表语言: Python PHP Swift

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

  • 缺点:不能很好的处理循环引用,而实时维护引用计数也是有损耗的。

  1. 标记-清除:从根变量开始遍历所有引用的对象,标记引用的对象,没有被标记的进行回收。

  • 代表语言: Golang (三色标记法)

  • 优点:解决了引用计数的缺点。

  • 缺点:需要 STW,暂时停掉程序运行。

  1. 分代收集:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,短的放入新生代,不同代有不同的回收算法和回收频率。

  • 代表语言: Java

  • 优点:回收性能好

  • 缺点:算法复杂

Golang 垃圾回收

跳过原理,我们先来介绍 Golang 的三色标记法。

三色标记法

三色标记法只是为了叙述方便而抽象出来的一种说法,实际上的对象是没有三色之分的。这里的三色,对应了垃圾回收过程中对象的三种状态:

  • 灰色:对象还在标记队列中等待

  • 黑色:对象已被标记, gcmarkBits  对应位为  1  -- 该对象不会在本次 GC 中被回收

  • 白色:对象未被标记, gcmarkBits  对应位为  0  -- 该对象将会在本次 GC 中被清理

具体流程如下图:

rIrAz2e.jpg!mobile

三色标记法

回收原理

通过上图,应该对三色标记法有了一个比较直观的了解,那么我们现在来讲讲原理。简单的讲,就是标记内存中那些还在使用中(即被引用了)的部分,而内存中不再使用(即未被引用)的部分,就是要回收的垃圾,需要将其回收,以供后续内存分配使用。上图中的 A、B、D 就是被引用正在使用的内存,而C、F、E 曾经被使用过,但现在没有任何对象引用,就需要被回收掉。

而 Root 区域主要是程序运行到当前时刻的栈和全局数据区域,是实时正在使用到的内存,当然应该优先标记。而考虑到内存块中存放的可能是指针,所以还需要递归的进行标记,待全部标记完后,就会对未被标记的内存进行回收。

内存标记

golang 中采用 span 数据结构管理内存,span 中维护了一个个内存块,并由一个位图  allocBits  表示内存块的分配情况,而上文中提到的  gcmarkBits  是记录每块内存块被引用情况的。

zMnyu26.jpg!mobile

内存标记

如上图, allocBits  记录了每块内存的分配情况,而  gcmarkBits  记录了每块内存的标记情况。在标记阶段会对每块内存进行标记,有对象引用的内存标记为 1,没有对象引用的为 0。而  allocBits  和  gcmarkBits  的数据结构是完全一样的,在结束标记后,将  allocBits  指向  gcmarkBits ,则有标记的才是存活的,这样就完成了内存回收。而  gcmarkBits  则会在下次标记时重新分配内存。

垃圾回收优化

在前文中提到,golang 的垃圾回收算法属于  标记-清除 ,是需要 STW 的。STW 就是  Stop The World  的意思,在 golang 中就是要停掉所有的 goroutine,专心进行垃圾回收,待垃圾回收结束后再恢复 goroutine。而 STW 时间的长短直接影响了应用的执行,如果时间过长,那将是灾难性的。为了缩短 STW 时间,golang 不对优化垃圾回收算法,其中 写屏障(Write Barrier) 辅助GC(Mutator Assist) 就是两种优化垃圾回收的方法。

  • 写屏障(Write Barrier) :上面说到的 STW 的目的是防止 GC 扫描时内存变化引起的混乱,而写屏障就是让 goroutine 与 GC 同时运行的手段,虽然不能完全消除 STW,但是可以大大减少 STW 的时间。写屏障在 GC 的特定时间开启,开启后指针传递时会把指针标记,即本轮不回收,下次 GC 时再确定。

  • 辅助 GC(Mutator Assist) :为了防止内存分配过快,在 GC 执行过程中,GC 过程中 mutator 线程会并发运行,而 mutator assist 机制会协助 GC 做一部分的工作。

垃圾回收触发机制

  1. 内存分配量达到阈值 :每次内存分配都会检查当前内存分配量是否达到阈值,如果达到阈值则触发 GC。 阈值 = 上次 GC 内存分配量 * 内存增长率 ,内存增长率由环境变量  GOGC  控制,默认为 100,即每当内存扩大一倍时启动 GC。

  2. 定时触发 GC :默认情况下,2分钟触发一次 GC,该间隔由  src/runtime/proc.go  中的  forcegcperiod  声明。

  3. 手动触发 GC :在代码中,可通过使用  runtime.GC()  手动触发 GC。

GC 优化建议

由上文可知,GC 性能是与对象数量有关的,对象越多 GC 性能越差,对程序的影响也越大。所以在开发中要尽量减少对象分配个数,采用对象复用、将小对象组合成大对象或采用小数据类型(如使用  int8  代替  int )等。

结语

一门编程语言的垃圾回收机制会直接影响使用其开发应用的性能。在日常开发工作中也因注意到其作用,有助于开发出高性能的应用,这也是 GC 常常在面试中被问到的原因。同时,了解 GC 对了解内存管理也很有帮助。

jiYFzaM.gif!mobile

欢迎扫描二维码关注公众号,了解更多云原生知识


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK