Go语言使用什么样的垃圾回收机制?

118

Go是一种垃圾回收语言:

http://golang.org/doc/go_faq.html#garbage_collection

这里说它是标记-清除垃圾回收器,但没有深入探讨细节,并且正在研究替代方案……然而,自从Go发布以来,这段文字似乎并没有更新。

它仍然是标记-清除吗?它是保守式还是精确式的?它是分代的吗?


2
有关Go语言垃圾回收器截至2018年7月的详细讨论,请参阅https://blog.golang.org/ismmkeynote。 - Wildcard
5个回答

121

Go 1.4+垃圾回收器计划:

  • 混合式停顿/并发回收器
  • 停顿部分由10ms截止期限限制
  • CPU核心专门用于运行并发回收器
  • 三色标记-扫描算法
  • 非代际的
  • 非压缩的
  • 完全精确的
  • 如果程序移动指针,则会产生小的成本
  • 比Go 1.3 GC具有较低的延迟,但很可能也具有较低的吞吐量

在Go 1.1基础上更新的Go 1.3垃圾回收器:

  • 并发扫描(导致更小的暂停时间)
  • 完全精确的

Go 1.1垃圾回收器:

  • 标记-清除(并行实现)
  • 非代际
  • 非压缩
  • 大多数情况下是精确的(除了堆栈帧)
  • 停止世界
  • 基于位图的表示
  • 当程序不分配内存时,成本为零(也就是说,在 C 中移动指针的速度与 C 相同,尽管在实践中,这比 C 运行略慢,因为 Go 编译器不像 GCC 等 C 编译器那样先进)
  • 支持对象上的终结器
  • 不支持弱引用

Go 1.0 垃圾回收器:

  • 与 Go 1.1 相同,但垃圾回收器是保守的,而不是大多数情况下是精确的。保守的 GC 能够忽略诸如 []byte 之类的对象。

用不同的 GC 替换 GC 是有争议的,例如:

  • 除了非常大的堆之外,不清楚代际 GC 是否总体上更快
  • “unsafe” 包使得实现完全精确的 GC 和紧凑的 GC 变得困难

3
@uriel:是的,我在我的回答的第一项中提到了这一点——文本中写着“(并行实现)”。 - user811773
C#的垃圾收集器是精确的,就像在Go中一样,你可以引用结构体成员。C#有一个不安全模式,但我不确定它如何与Go的不安全实现相比较。 - skyde
也许是时候进行1.5版本的更新了?我在这个答案中提供了一些原始材料(有用的部分可能是早期结果报告的链接):https://dev59.com/C1wZ5IYBdhLWcg3wPOT2#31686469 - twotwotwo
3
更新这个答案,以记录1.5.x版本的历史记录。 - Ismael
@Ismael,很抱歉,我今年(2016年)没有时间处理这个问题。 - user811773
显示剩余3条评论

34

(对于Go 1.8 - Q1 2017,请参见下文)

下一个Go 1.5 并发垃圾收集器需要能够“调节”该gc。
这里提出了一份建议在这篇论文中, 这可能会使其适用于Go 1.5,但也有助于理解Go中的gc。

您可以在1.5之前看到状态(停止世界:STW)

在Go 1.5之前,Go使用了并行停止世界(STW)收集器。
虽然STW收集具有许多缺点,但它至少具有可预测和可控的堆增长行为。

https://40.media.tumblr.com/49e6556b94d75de1050c62539680fcf9/tumblr_inline_nr6qq8D9FE1sdck2n_540.jpg

(图片来自GopherCon 2015演讲“Go GC: Solving the Latency Problem in Go 1.5”)

STW(暂停所有线程)垃圾收集器的唯一调优参数是“GOGC”,即在两次垃圾回收之间堆大小相对增长的比例。默认设置为100%,每当堆大小超过上一次回收时的活动堆大小时,就会触发垃圾回收:

https://docs.google.com/drawings/image?id=sLJ_JvGfPfPnojLlEGLCWkw&rev=1&h=113&w=424&ac=1

STW(Stop The World)收集器中的GC时间。

Go 1.5引入了并发收集器。这比STW收集有很多优点,但由于应用程序可以在垃圾回收器运行时分配内存,因此使堆增长更难控制

https://40.media.tumblr.com/783c6e557b427a5c023520578740eb94/tumblr_inline_nr6qqpmaJx1sdck2n_540.jpg

(照片来自GopherCon 2015的演示文稿 "Go GC: Solving the Latency Problem in Go 1.5")

为了达到相同的堆增长限制,运行时必须更早地开始垃圾回收,但是要多早取决于许多变量,其中许多无法预测。

  • 如果启动回收器太早,应用程序将执行过多的垃圾回收,浪费CPU资源。
  • 如果启动回收器太晚,应用程序将超过所需的最大堆增长。

在不牺牲并发性能的前提下,实现正确的平衡需要仔细控制垃圾回收器。

GC pacing旨在优化两个方面:堆增长和垃圾回收器使用的CPU。

https://docs.google.com/drawings/image?id=sEZYCf7Mc0E0EGmy4gho3_w&rev=1&h=235&w=457&ac=1


GC pacing的设计由四个组件构成:
  1. 用于估计GC周期所需扫描工作量的估算器,
  2. 让mutators在堆分配达到堆目标时执行估计的扫描工作量的机制,
  3. 当mutator assist未充分利用CPU预算时进行后台扫描的调度程序,以及
  4. 用于GC触发器的比例控制器。
该设计平衡了时间的两个不同视角:CPU时间和堆时间。
  • CPU时间类似于标准挂钟时间,但速度快了GOMAXPROCS倍。
    也就是说,如果GOMAXPROCS为8,则每秒钟墙上的八秒钟会过去,GC每秒钟获得两秒钟的CPU时间。
    CPU调度程序管理CPU时间。
  • 堆时间的流逝以字节为单位,随着mutators的分配而向前移动。
堆时间与墙上时间的关系取决于分配率,并且可能会不断变化。 Mutator assists管理堆时间的流逝,确保在堆达到目标大小时已完成估计的扫描工作。 最后,触发器控制器创建一个反馈循环,将这两个时间视角联系起来,优化堆时间和CPU时间目标。

21

这是GC的实现:

https://github.com/golang/go/blob/master/src/runtime/mgc.go

从源代码中的文档可以看出:

GC与mutator线程并发运行,类型精确(即准确),允许多个GC线程并行运行。它是一个并发的标记和扫描算法,使用写屏障技术。它不是分代式的,也不会压缩内存。分配内存时使用基于P的大小分类分配区域,以最小化内存碎片,同时在常见情况下消除锁定。


10
Go 1.8 GC可能会再次演变,提案“消除STW堆栈重新扫描”与之有关。
截至Go 1.7,无界和潜在的非平凡停止-世界(STW)时间的唯一剩余来源是堆栈重新扫描。我们建议通过切换到混合写屏障来消除对堆栈重新扫描的需要,该写屏障结合了Yuasa风格的删除写屏障[Yuasa '90]Dijkstra风格的插入写屏障[Dijkstra '78]。初步实验表明,这可以将最坏情况下的STW时间降低到50微秒以下,并且这种方法可能使消除STW标记终止成为可能。

公告在这里,你可以看到相关的源代码提交是d70b0fe和之前。


3

我不确定,但我认为当前的GC(tip)已经是并行的,或者至少正在进行中。因此,停止世界属性不再适用于近期或者不会在将来适用。也许其他人可以更详细地澄清这一点。


7
这是一个“停止世界”的过程。垃圾回收在世界停止后可能会并行运行。你可能想表达的是“并发垃圾回收”。 - user811773

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接