golang中GC触发时机

Go 语言中对 GC 的触发时机存在两种形式:

  1. 主动触发,通过调用 runtime.GC 来触发 GC,此调用阻塞式地等待当前 GC 运行完毕。
  2. 被动触发,分为两种方式:
  • 使用系统监控,当超过两分钟没有产生任何 GC 时,强制触发 GC。
  • 使用步调(Pacing)算法,其核心思想是控制内存增长的比例。

golang三色标记清除法
v1.5之后使用了三色标记清除

go语言如何运行 go语言 gc_golang

1.程序启初创建,全部标记为白色,将所有对象放入白色集合中

go语言如何运行 go语言 gc_复用_02

2.遍历root set(非递归形式,只遍历一次),得到灰色节点,将灰色节点放入灰色集合中。

go语言如何运行 go语言 gc_赋值_03

3.遍历灰色的标记表,将可达到的对象从白色标记为灰色,其本身从灰色变成黑色。重复此步骤,直到灰色标记表中无任何对象

go语言如何运行 go语言 gc_调优_04

4.删除所有的白色对象

2.为什么GC过程中需要STW?

go语言如何运行 go语言 gc_调优_05

如果没有STW,发生上面的情况,会把引用的对象删除,造成数据混乱。

2.如何进行GC调优

当我们谈论 GC 调优时,通常是指减少用户代码对 GC 产生的压力,这一方面包含了减少用户代码分配内存的数量(即对程序的代码行为进行调优),另一方面包含了最小化 Go 的 GC 对 CPU 的使用率(即调整 GOGC)。

GC 的调优是在特定场景下产生的,并非所有程序都需要针对 GC 进行调优。只有那些对执行延迟非常敏感、当 GC 的开销成为程序性能瓶颈的程序,才需要针对 GC 进行性能调优,几乎不存在于实际开发中 99% 的情况。

总的来说,我们可以在现在的开发中处理的有以下几种情况:

  1. 对停顿敏感:GC 过程中产生的长时间停顿、或由于需要执行 GC 而没有执行用户代码,导致需要立即执行的用户代码执行滞后。
  2. 对资源消耗敏感:对于频繁分配内存的应用而言,频繁分配内存增加 GC 的工作量,原本可以充分利用 CPU 的应用不得不频繁地执行垃圾回收,影响用户代码对 CPU 的利用率,进而影响用户代码的执行效率。

从这两点来看,所谓 GC 调优的核心思想也就是充分的围绕上面的两点来展开:优化内存的申请速度,尽可能的少申请内存,复用已申请的内存。或者简单来说,不外乎这三个关键字:控制、减少、复用

例1:合理化内存分配的速度、提高赋值器的 CPU 利用率

使用goroutine池,减少调度器压力,优化程序中赋值器对 CPU 的使用率

例2:降低并复用已经申请的内存

sync.Pool 内存复用或bytesbuffpool

例3:调整 GOGC

我们已经知道了 GC 的触发原则是由步调算法来控制的,其关键在于估计下一次需要触发 GC 时,堆的大小。可想而知,如果我们在遇到海量请求的时,为了避免 GC 频繁触发,是否可以通过将 GOGC 的值设置得更大,让 GC 触发的时间变得更晚,从而减少其触发频率,进而增加用户代码对机器的使用率呢?答案是肯定的。

在实际实践中可表现为需要紧急处理一些由 GC 带来的瓶颈时,人为将 GOGC 调大,加钱加内存,扛过这一段峰值流量时期。

当然,这种做法其实是治标不治本,并没有从根本上解决内存分配过于频繁的问题,极端情况下,反而会由于 GOGC 太大而导致回收不及时而耗费更多的时间来清理产生的垃圾,这对时间不算敏感的应用还好,但对实时性要求较高的程序来说就是致命的打击了。

因此这时更妥当的做法仍然是,定位问题的所在,并从代码层面上进行优化。

现在我们来总结一下前面三个例子中的优化情况:

  1. 控制内存分配的速度,限制 goroutine 的数量,从而提高赋值器对 CPU 的利用率。
  2. 减少并复用内存,例如使用 sync.Pool 来复用需要频繁创建临时对象,例如提前分配足够的内存来降低多余的拷贝。
  3. 需要时,增大 GOGC 的值,降低 GC 的运行频率。