Go GC Q&A

111 阅读4分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

GC在什么时候触发?如何调整触发频率?

主动触发

通过runtime.GC来触发GC,阻塞式地等待当前GC运行完毕。

被动触发

  • 当超过两分钟没有产生任何GC时,强制触发GC。
  • 使用步调(Pacing)算法,核心思想是控制内存增长的比例。

如何主动触发GC?

通过runtime.GC来触发GC

GC分为几个阶段?分别做什么?

以STW为界限,可以将GC划分为5个阶段:

阶段说明赋值器状态
SweepTermination清扫终止阶段,为下一个阶段的并发标记做准备工作,启动写屏障STW
Mark扫描标记阶段,与赋值器并发执行,写屏障开启并发
MarkTermination标记终止阶段,保证一个周期内标记任务完成,停止写屏障STW
GCoff内存清扫阶段,将需要回收的内存归还到堆中,写屏障关闭并发
GCoff内存归还阶段,将过多的内存归还给操作系统,写屏障关闭并发

GC采用的三色标记法原理是什么?在哪个阶段需要STW?

理解三色标记法的关键是理解对象的三色抽象以及波面(wavefront)推进这两个概念。

三色抽象

从垃圾回收器的视角来看,三色抽象规定了三种不同类型的对象,并用不同的颜色相称:

  • 白色对象(可能死亡):未被回收器访问到的对象。在回收开始阶段,所有对象均为白色,当回收结束后,白色对象均不可达。
  • 灰色对象(波面):已被回收器访问到的对象,但回收器需要对其中的一个或多个指针进行扫描,因为他们可能还指向白色对象。
  • 黑色对象(确定存活):已被回收器访问到的对象,其中所有字段都已被扫描,黑色对象中任何一个指针都不可能直接指向白色对象。

波面

当垃圾回收开始时,只有白色对象。随着标记过程开始进行时,灰色对象开始出现(着色),这时候波面便开始扩大。当一个对象的所有子节点均完成扫描时,会被着色为黑色。当整个堆遍历完成时,只剩下黑色和白色对象,这时的黑色对象为可达对象,即存活;而白色对象为不可达对象,即死亡。这个过程可以视为以灰色对象为波面,将黑色对象和白色对象分离,使波面不断向前推进,直到所有可达的灰色对象都变为黑色对象为止的过程。

常见有哪些手段优化GC STW时间?

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

Golang内存分配算法是什么样的?

编程语言的内存分配器一般包含两种分配方法。

线性分配器

在内部维护一个指向内存特定位置的指针,当用户程序向分配器申请内存,分配器只需检查剩余的空闲内存、返回分配的内存区域并修改指针在内存中的位置

优点:较快的执行速度,较低的实现复杂度

缺点:无法在内存被释放时重用内存

空闲链表分配器

在内部维护一个类似链表的数据结构,当用户程序申请内存时,空闲链表分配器会依次遍历空闲的内存块,找到足够大的内存,然后申请新的资源并修改链表

优点:可以重新利用回收的资源

缺点:复杂度略高

该分配器有四种分配策略

  • 首次适应(First-Fit)— 从链表头开始遍历,选择第一个大小大于申请内存的内存块;
  • 循环首次适应(Next-Fit)— 从上次遍历的结束位置开始遍历,选择第一个大小大于申请内存的内存块;
  • 最优适应(Best-Fit)— 从链表头遍历整个链表,选择最合适的内存块;
  • 隔离适应(Segregated-Fit)— 将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块;

Go使用的是第四种隔离适应策略,隔离适应的分配策略减少了需要遍历的内存块数量,提高了内存分配的效率。

核心理念是使用多级缓存将对象根据大小分类,并按照类别实施不同的分配策略。