垃圾回收(Garbage Collection),是一种自动管理内存的机制。传统编程语言(如C/C++)需要开发者对无用内存资源进行手动释放。而 Go 则是通过 runtime 实现对内存资源的管理,自动释放无用内存资源。

常见的 GC 算法有很多,最常用的莫过于分代算法。而 Go 则选用了三色标记法。

Go GC 与 分代 GC 的差异点对比

分代 GC 回收的那些存活时间短的对象在 Go 中是直接被分配到栈上,当 goroutine 死亡后栈也会被直接回收,不需要 GC 的参与。只有那些需要长期存在的对象才会被分配到需要进行垃圾回收的堆中。

三色标记法

三色标记法将对象分为三类:

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

标记过程:

  1. 起初所有的对象都是白色的;
  2. 从根对象出发扫描所有可达对象,标记为灰色,放入待处理队列;
  3. 从待处理队列中取出灰色对象,将其引用的对象标记为灰色并放入待处理队列中,自身标记为黑色;
  4. 重复步骤3,直到待处理队列为空,此时白色对象即为不可达的“垃圾”,回收白色对象;

根对象在垃圾回收的术语中又叫做根集合,它是垃圾回收器在标记过程时最先检查的对象。

根对象包括:

  • 全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。
  • 执行栈:每个 goroutine 都包含自己的执行栈,这些执行栈上包含栈上的变量及指向分配的堆内存区块的指针。
  • 寄存器:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块。

Go GC 需要 STW 的原因:

为了保证准确性、防止无止境的内存增长等问题而不可避免的需要停止赋值器进一步操作对象图以完成垃圾回收。

屏障机制分类:

  • 插入屏障
  • 删除屏障
  • 混合屏障


Go GC过程

阶段1:Mark Setup 标记准备

会开启屏障,以保证三色标记能在并发情况下正确运行。(期间会STW)

阶段2:Marking 标记

本阶段会与项目代码一起并发执行。从各个根节点开始遍历,为对象进行着色。

在标记开始的时候,收集器会默认抢占 25% 的 CPU 性能,剩下的75%会分配给程序执行。但是一旦收集器认为来不及进行标记任务了,就会改变这个 25% 的性能分配。这个时候收集器会抢占程序额外的 CPU,这部分被抢占 goroutine 有个名字叫 Mark Assist。而且因为抢占 CPU的目的主要是 GC 来不及标记新增的内存,那么抢占正在分配内存的 goroutine 效果会更加好,所以分配内存速度越快的 goroutine 就会被抢占越多的资源。

阶段3:Mark Termination 标记结束

这个阶段会关闭掉阶段1开启的屏障,并计算下一次清理的目标和计划。(本阶段会STW)

阶段4:Sweeping 清理

本阶段会并发执行,清除前面标记出来需清理的内存。