Go GC

浅浅的花香味﹌ 2022-02-03 10:21 288阅读 0赞

Go GC

Go,你可以很容易地创建出低延时的应用。Go GC 似乎比其他语言的运行时要简单得多。对于 Go 1.10 版本,它的垃圾收集器是Concurrent Mask & Sweep (CMS) ,它不是压缩的,也不是分代的。这一点跟JVM 不同。

它是一个,并行标记,用一个写屏障(写的时候阻塞)的清理(程序)。它是非分代,非压缩的。 -- mgc.go
下面是 Java GCGo GC 的对比。相比于 JavaGo 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 使用的收集器,就是非迁移的。一般来说,如果你在非迁移的垃圾收集器中,重复地进行内存分配跟释放,最终将导致堆碎片,从而降低分配(堆内存)的性能。但,当然,这也取决于你的内存分配器如何实现。

1533208430578.png

迁移的 GC(moving GC)

移动垃圾收集器将活动对象移动到堆的末尾来压缩堆。移动垃圾收集器的一个实例是拷贝 GC(Copying GC),它在 HotSpot VM 中使用。

1533208420073.png

压缩具有如下优点:

  • 避免碎片
  • 依靠于压缩的分配,可以实现一个高性能的内存分配器(因为所有对象都位于堆的末尾,所以我们可以在右边,最后的位置,增加新的内存。)

1533208398145.png

为什么 Go 不选择压缩的方式(Why Go did not opt for compaction)

来自 Google 的 Rick Hudson,在国际内存管理研讨会(ISMM)上,在他的 keynote 中分享到,Getting To Go。

  • 在 2014 年,他们最初计划做一个任意读的并行拷贝 GC。
  • 由于没有时间 - 彼时,他们正在将用 C 写的运行时改成用 Go 实现(对运行时的修改) - 他们决定选择 CMS。
  • 他们采用了基于 TCMalloc 的内存分配器,解决碎片和优化(内存)分配的问题。

了解更多 Go 内存分配的内容,请看运行时 的评论。

  • 这最初基于 tcmalloc,但已经修改了很多的地方。- malloc.go

分代的 GC(Generational GC)

分代 GC 的目的是通过将堆中对象除以它的年龄(他们从 GC 中存活的次数)来优化 GC,从而产生分代。分代假说指出,在许多应用中,新事物大多年轻。基于该假设,通过以下策略来 (优化)GC,也就是说,我们可以取消多次对旧对象的扫描。

  • 从年轻的空间(Minor GC)中更频繁地收集垃圾。
  • 可以将它们,在空间中已经存活了几个 GC 周期的旧对象,重新放置在不经常收集垃圾的空间(Major GC)中

Java8 HotSpot Vm 的所有收集器都实现了分代 GC。

写入屏障(Write barrier)

分代 GC 的缺点是,即使垃圾收集没有运行,对于应用程序也有开销。我们来看一个 Minor GC 的例子。

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 功能?

  • 这是个很好的问题。Go 当前的 GC 显然做了一些额外的工作,但它也跟其他的工作并行执行,所以在具有备用 CPU 的系统上,Go 正在作出合理的选择。请看 https://golang.org/issue/17969

结束语(Closing notes)

通过研究 Go 垃圾收集器,我能够理解 Go GC 当前结构的背景以及它如何克服它的弱点。Go 发展得非常快。如果你对 Go 感兴趣,最好继续留意它(当我写这篇文章时,2018 年 8 月,Go 发布了它的 1.11 版本)。

发表评论

表情:
评论列表 (有 0 条评论,288人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Go语言 GC优化经验分享

    不想看长篇大论的,这里先给个结论,go的gc还不完善但也不算不靠谱,关键看怎么用,尽量不要创建大量对象,也尽量不要频繁创建对象,这个道理其实在所有带gc的编程语言也都通用。

    相关 Go GC

    Go GC 用 `Go`,你可以很容易地创建出低延时的应用。`Go GC` 似乎比其他语言的运行时要简单得多。对于 `Go 1.10` 版本,它的垃圾收集器是`Concurr

    相关 go笔记-GC

    GO “非分代的、非紧缩、写屏障、并发标记清理” 并发清理: 垃圾回收(清理过程)与用户逻辑并发执行 三色并发标记 : 标记与用户逻辑并发执行 一般常用垃圾回收方法