Go GC

gogeof · · 3051 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

大家好,我是 Okada([@ocadaruma](https://twitter.com/ocadaruma)),LINE 广告平台团队的成员。我对 Go 的 GC (垃圾收集器)有点感兴趣,甚至还促使我专门写一篇关于它的博客。Go 是一门由 Google 开发,并且支持垃圾收集的编程语言。Go 通过[管道](https://tour.golang.org/concurrency/2) 支持并发。很多的公司,包括 Google,都在使用 Go,LINE 也用 Go 来开发工具和服务。 ## Go GC 用 Go,你可以很容易地创建出低延时的应用。Go GC 似乎比其他语言的运行时要简单得多。对于 [Go 1.10](https://golang.org/doc/go1.10) 版本,它的垃圾收集器是 Concurrent Mask & Sweep (CMS) ,它不是压缩的,也不是分代的。这一点跟 JVM 不同。 - 它是一个,并行标记,用一个写屏障(写的时候阻塞)的清理(程序)。它是非分代,非压缩的。 -- [mgc.go](https://github.com/golang/go/blob/4d7cf3fedbc382215df5ff6167ee9782a9cc9375/src/runtime/mgc.go) 下面是 Java GC 和 Go GC 的对比。相比于 Java ,Go GC 对于我来说看起来有点简单,所以我决定深入进去,看下 Go GC 是怎么工作的。 | | Java(Java8 HotSpot VM) | Go | :- | :- | :- | | Collector | Several collectors (Serial, Parallel, CMS, G1) | CMS | | Compaction | Compacts | Does not compact | | Generational GC | Generational GC | Non-generational GC | | Tuning parameters | Depends on the collector. Multiple parameters available. | GOGC only | ## 压缩(Compaction) 垃圾收集可以选择不迁移或者迁移(堆上的对象)。 ## 不迁移的 GC(Non-moving GC) 不迁移的垃圾收集不会在堆中给对象重新分配内存。CMS,Go 使用的收集器,就是非迁移的。一般来说,如果你在非迁移的垃圾收集器中,重复地进行内存分配跟释放,最终将导致堆碎片,从而降低分配(堆内存)的性能。但,当然,这也取决于你的内存分配器如何实现。 ![](https://engineering.linecorp.com/image/2018/8/2/1533208430578.png) ## 迁移的 GC(moving GC) 移动垃圾收集器将活动对象移动到堆的末尾来压缩堆。移动垃圾收集器的一个实例是拷贝 GC(Copying GC),它在 HotSpot VM 中使用。 ![](https://engineering.linecorp.com/image/2018/8/2/1533208420073.png) 压缩具有如下优点: - 避免碎片 - 依靠于压缩的分配,可以实现一个高性能的内存分配器(因为所有对象都位于堆的末尾,所以我们可以在右边,最后的位置,增加新的内存。) ![](https://engineering.linecorp.com/image/2018/8/2/1533208398145.png) ## 为什么 Go 不选择压缩的方式(Why Go did not opt for compaction) 来自 Google 的 Rick Hudson,在国际内存管理研讨会(ISMM)上,在他的 keynote 中分享到,[Getting To Go](https://blog.golang.org/ismmkeynote)。 - 在 2014 年,他们最初计划做一个任意读的并行拷贝 GC。 - 由于没有时间 - 彼时,他们正在将用 C 写的运行时改成用 Go 实现(对运行时的修改) - 他们决定选择 CMS。 - 他们采用了基于 TCMalloc 的内存分配器,解决碎片和优化(内存)分配的问题。 了解更多 Go 内存分配的内容,请看[运行时](https://golang.org/doc/go1.4#runtime) 的评论。 - 这最初基于 [tcmalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html),但已经修改了很多的地方。- [malloc.go](https://github.com/golang/go/blob/release-branch.go1.10/src/runtime/malloc.go#L7) ## 分代的 GC(Generational GC) 分代 GC 的目的是通过将堆中对象除以它的年龄(他们从 GC 中存活的次数)来优化 GC,从而产生分代。分代假说指出,在许多应用中,新事物大多年轻。基于该假设,通过以下策略来 (优化)GC,也就是说,我们可以取消多次对旧对象的扫描。 - 从年轻的空间(Minor GC)中更频繁地收集垃圾。 - 可以将它们,在空间中已经存活了几个 GC 周期的旧对象,重新放置在不经常收集垃圾的空间(Major GC)中 Java8 HotSpot Vm 的所有收集器都实现了分代 GC。 ## 写入屏障(Write barrier) 分代 GC 的缺点是,即使垃圾收集没有运行,对于应用程序也有开销。我们来看一个 Minor GC 的例子。 ![](https://engineering.linecorp.com/image/2018/8/2/1533208408997.png) 如果我们仅检查 root 用于指向 Minor 的指针,然后收集无法访问的对象,那么旧对象中引用新对象(如图中的 obj2)会被意外地收集。但是,如果我们检查整个堆,包括旧对象以避免收集 Minor 对象(时产生的问题),那么对于分代 GC 来说就没有意义。因此,添加一个进程,以便在替换或重写引用时将旧对象的引用记录保存到新对象中。我们将此额外流程称为写入屏障。使用分代 GC 可能有更多好处,可以弥补这个缺点(写入屏障开销)。 ## 为什么不使用分代 GC ?(Why not generational GC?) 正如我们之前看到的,分代垃圾收集器需要一个写屏障来记录代之间的指针。回到 Rick Hudson 的主题演讲,Getting To Go,我们可以看到他们确实考虑过分代 GC,但由于写屏障开销而放弃了它。 > 写屏障速度很快,但简单来说,它还不够快。 使用 Go,编译器的逃逸分析非常出色,如果需要,程序员可以控制到,不在堆上分配对象,因此短期对象通常分配在栈中而不是在堆中;这意味着不需要 GC。总的来说,你从分代 GC 得到的(好处)比其他(语言)运行时少。有一些用 Go 语言编写的库,跟速度一样出名的是,这些库恰好也是零内存分配。尽管如此,我们仍然有消耗,在每次 GC 循环中多次扫描长寿命的对象。来自 Google 的 Ian Lance Taylor 已经在 Golang-nuts 中提到了这一点,[为什么垃圾收集器不实现分代 GC 功能?](https://groups.google.com/forum/#!topic/golang-nuts/KJiyv2mV2pU) - 这是个很好的问题。Go 当前的 GC 显然做了一些额外的工作,但它也跟其他的工作并行执行,所以在具有备用 CPU 的系统上,Go 正在作出合理的选择。请看 [https://golang.org/issue/17969](https://golang.org/issue/17969) ## 结束语(Closing notes) 通过研究 Go 垃圾收集器,我能够理解 Go GC 当前结构的背景以及它如何克服它的弱点。Go 发展得非常快。如果你对 Go 感兴趣,最好继续留意它(当我写这篇文章时,2018 年 8 月,Go 发布了它的 1.11 版本)。

via: https://engineering.linecorp.com/en/blog/detail/342/

作者:Haruki Okada  译者:gogeof  校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3051 次点击  ∙  1 赞  
加入收藏 微博
被以下专栏收入,发现更多相似内容
1 回复  |  直到 2018-11-16 17:25:49
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传