17

每位 Gopher 都应该了解的 Golang 语言的垃圾回收算法

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzA4Mjc1NTMyOQ%3D%3D&%3Bmid=2247484378&%3Bidx=1&%3Bsn=8ee28fac11c663fa2146f1d9da28c86d
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.

01 

介绍

关于垃圾回收,比较常见的算法有引用计数、标记清除和分代收集。Golang 语言使用的垃圾回收算法是标记清除。本文主要介绍一下 Golang 语言的垃圾回收算法。

02 

Golang 语言 v1.3 及之前的垃圾回收 - 标记清除

Golang 语言的标记清除垃圾回收算法,为了防止 GC 扫描时内存变化引起的混乱,那么就需要 STW,即 Stop The World,具体在 Golang 语言中是指,在 GC 时,先停止所有 goroutine,再进行垃圾回收,等待垃圾回收结束后再恢复所有被停止的 goroutine。 关于 STW 执行流程,可以参考下面这张经典图片。

Y7baiyn.jpg!mobile

「图片来自网络」

标记清除:

  1. 启动 STW,暂停程序的业务逻辑,找出不可达对象和可达对象。

  2. 将所有可达对象做标记。

  3. 清除未标记的对象。

  4. 停止 STW,程序继续执行。

  5. 循环往复,直到进程程序生命周期结束。

标记清除的缺点:

  1. STW 需要暂停程序,导致程序卡顿。

  2. 做标记需要扫描整个 heap(堆)。

  3. 清除数据会产生 head(堆)碎片。

标记清除的优化:

因为 STW 需要暂停程序,为了减少暂停程序的时间,将清除操作移出 STW 执行周期,但是优化效果不明显,进一步优化请继续阅读下文。

03 

Golang 语言 v1.5 的垃圾回收 - 三色标记

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

  • 白色:对象未被标记, gcmarkBits 对应位为 0,该对象将会在本次 GC 中被清理。
  • 灰色:对象还在标记队列中等待被标记。

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

三色标记:

  1. 新创建的对象,默认标记为白色。

  2. 从根节点开始遍历所有白色对象,将遍历到的对象的颜色由白色改为灰色。

  3. 将灰色对象作为根节点开始遍历所有白色对象,将遍历到的对象的颜色由白色改为灰色,并将作为根节点的灰色对象的颜色由灰色改为黑色。

  4. 循环往复,直到所有灰色对象的颜色都变为黑色。

  5. 将剩余的白色对象全部清除。

三色标记的缺点:

一个不被灰色对象可达的白色对象,如果被一个黑色对象引用,将会造成该白色对象丢失的问题。

三色标记的优化:

Golang 官方通过强/弱三色不变性,对三色标记做了优化。强三色不变性,即强制性不允许黑色对象引用白色对象;

弱三色不变性,即黑色对象可以引用白色对象,但是必须满足一个条件,该白色对象必须有灰色对象对它的直接引用,或者是可达链路中包含灰色对象。

具体实现是通过写屏障(Write Barrier),即在 GC 的特定时间开启,开启后指针传递时会把指针标记,被标记的指针在本次 GC 过程中不会被清理,等到下次 GC 时,才会被清理。写屏障的目的就是为了缩短 STW 的时间,让 goroutine 和 GC 同时运行。

Golang 语言中的写屏障分为插入写屏障和删除写屏障。

插入写屏障的含义:

满足强三色不变性,即被引用对象,会被强制标记为灰色。

插入写屏障的缺点:

结束时需要 STW 重新扫描栈,大约需要 10-100ms。

删除写屏障的含义:

满足弱三色不变性,即被删除对象,如果自身为灰色或者白色,会被标记为灰色。

删除写屏障的缺点:

回收精度低,即一个对象即使被删除了,最后一个指向该对象的指针也会等到下一次 GC 回收中才被清理。

04 

Golang 语言 v1.8 的垃圾回收 - 混合写屏障

Golang 语言的团队为了更进一步优化垃圾回收,采用了混合写屏障。

混合写屏障:

  1. 无需使用 STW,GC 在首次执行时,先将栈上的所有对象都标记为黑色。

  2. GC 在执行过程中,在栈上新创建的对象,默认被标记为黑色。

  3. 将被删除的对象标记为灰色。

  4. 将被添加的对象标记为灰色。

混合写屏障的优点:

混合写屏障,满足弱三色不变性,结合了插入写屏障和删除写屏障的优点。

05 

Golang 语言的 GC 触发方式

  1. 内存分配阈值, 阈值=上次 GC 内存分配值 * 内存增长率 ,其中内存增长率由环境变量  GOGC 设定,默认值为 100。每次内存分配时,都会先检查当前内存分配是否已经达到阈值,如果已达到阈值,就会触发 GC,即每当内存分配量将要增长一倍时则触发 GC。
  2. 定时触发, src/runtime/proc.go 文件中的  forcegcperiod 设定触发 GC 的时间间隔,默认值为 2 分钟。
  3. 手动触发,通过调用  runtime.GC() 方法,触发 GC。

06 

调式 GC

Golang 语言使用 GODEBUG 调式 GC: GODEBUG=gctrace=1 go run main.go

输出结果:

gc 1 @0.013s 0%: 0.037+0.36+0.004 ms clock, 0.60+0.48/0.81/0.012+0.073 ms cpu, 4->4->0 MB, 5 MB goal, 16 P
gc 2 @0.016s 1%: 0.010+0.24+0.004 ms clock, 0.17+0.29/0.44/0.21+0.064 ms cpu, 4->4->0 MB, 5 MB goal, 16 P
gc 3 @0.019s 1%: 0.069+0.50+0.041 ms clock, 1.1+0.29/0.68/0.13+0.66 ms cpu, 4->4->0 MB, 5 MB goal, 16 P
gc 4 @0.021s 2%: 0.056+0.35+0.041 ms clock, 0.90+0.33/0.67/0.064+0.65 ms cpu, 4->4->0 MB, 5 MB goal, 16 P
gc 5 @0.023s 2%: 0.053+0.27+0.003 ms clock, 0.85+0.42/0.63/0.069+0.057 ms cpu, 4->4->0 MB, 5 MB goal, 16 P

输出结果的含义:

  1. gc 1 @0.013s 0%: 表示第 1 次执行 GC, 0.013s 表示执行时间。
  2. 0% GC 占用进程的进程 CPU 时间的百分比。
  3. 0.037+0.36+0.004 ms clock 表示 GC 耗时,依次是 STW 清扫的时间,并发标记和扫描的时间,STW 标记的时间,即  stop-the-world (STW) sweep termination + concurrent mark and scan + and STW mark termination
  4. 0.60+0.48/0.81/0.012+0.073 ms cpu GC 占用的 CPU 时间。
  5. 4->4->0 MB 依次表示堆的大小,GC 后堆的大小,存活堆的大小。
  6. 5 MB goal 表示整体堆的大小。
  7. 16 P 表示 CPU 的核心数。
  8. GC forced 表示调用  runtime.GC() 方法,手动执行 GC。
scvg0: inuse: 6, idle: 12, sys: 18, released: 0, consumed: 18 (MB)  
scvg0: inuse: 6, idle: 9, sys: 15, released: 0, consumed: 15 (MB)
GC forced
  • inuse:内存使用大小。

  • idle:需要清除的空闲内存。

  • sys: 系统映射的内存。

  • released:释放的系统内存。

  • consumed:申请的系统内存。

07

总结

本文通过 Golang 语言的 v1.3、v1.5 和 v1.8 三个版本的 Golang 语言的算法的演进介绍垃圾回收。实际上几乎每个版本都会涉及垃圾回收的优化,相关代码也越来越复杂。如果读者希望更深入了解垃圾回收相关的内容,建议阅读相关源码。

尽管 Golang 语言可以自动进行垃圾回收,但是 GC 也会消耗资源,尽量还是在编写 Golang 代码的时候减少对象分配的数量,采用对象复用、将小对象组合成大对象或采用精准的数据类型,比如可以使用 int8,绝不使用 int。还可以在编写 Golang 代码的时候,手动触发 GC,将不再使用的内存及时释放。

关注微信公众号,加入读者微信群

发送关键字「资料」,免费获取 Go 语言学习资料。

fEBBJ3f.png!mobile

推荐阅读:

Golang 语言的内存管理

Golang 语言的 goroutine 调度器模型 GPM

参考资料:

https://blog.golang.org/ismmkeynote

https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/

https://github.com/aceld/golang

https://www.geek-share.com/detail/2771766860.html

https://developer.aliyun.com/article/775798

https://juejin.cn/post/6844903917650722829

:arrow_down:更多精彩内容,请点击「 阅读原文


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK