相关概念
栈对象:指分配在函数调用栈(goroutine stack)上的内存,通常是局部变量(如基本类型、结构体、数组等),它们的生命周期与所属的函数调用绑定,函数返回时被自动回收,无需垃圾回收(GC)干预。
堆对象:指分配在全局堆(heap)上的内存,它们的生命周期由垃圾回收器(GC) 管理,通过可达性分析(从根对象出发)决定是否回收。 即使引用该堆对象的goroutine已退出,只要其他根对象(如全局变量、其他goroutine栈)仍引用它,对象就存活;否则成为垃圾。
root对象:根对象是指程序代码不需要通过其他对象就可以直接访问到的对象,通过Root对象, 可以追踪到其他存活的对象。
STW:即"stop the word",GC期间某个阶段会停止所有的用户代码,中断程序逻辑,以确定引用关系。
标记清理阶段
- 从root对象遍历可达的所有对象标记为存活状态
- 遍历堆中的所有对象,释放没有被标记的内存空间
- 用户程序在垃圾收集的过程中不能执行(STW)
标记清理的最大弊端就是在整个GC期间需要STW,将整个程序暂停。因为如果不进行STW的话,会出现已经被标记的对象A,引用了新的未被标记的对象B,但由于对象A已经标记过了,不会再重新扫描A对B的可达性,从而将B对象当做垃圾回收掉。
三色抽象阶段
- 白色:初始状态,表示未被扫描或不可达(可能是垃圾),如果标记阶段结束后仍为白色,则会被回收。
- 灰色:中间状态,表示对象本身已标记为存活,但其直接引用的对象还未被扫描。
- 黑色:完成状态,表示 已被扫描,且所有引用的子对象也已被扫描
三色标记垃圾收集器的工作原理很简单,我们可以将其归纳成以下几个步骤:
- 初始化时所有对象被标记为白色
- GC开始时,遍历root set,讲直接可达的对象标记为灰色
- 遍历灰色对象,将其直接可达的对象标记为灰色,自身标记为黑色
- 重复第三步,直到没有灰色对象
- 清理剩下的白色对象
但此时Golang依然需要STW来保证标记结果的正确性,不过总结来看,在三色标记法的过程中对象丢失,需要同时满足下面两个条件:
- 条件一:白色对象被黑色对象引用
- 条件二:灰色对象与白色对象之间的可达关系遭到破坏
一句话简单来讲就是:对象间的引用关系从“灰到白”变成了“黑到白”
所以只要把上面两个条件破坏一个,就可以保证对象不丢失,所以我们的golang团队就提出了两种破坏条件的方式:
- 强三色不变式:不允许黑色对象引用白色对象
- 弱三色不变式:黑色对象可以引用白色对象,但白色对象必须直接或间接被灰色对象引用
屏障技术
屏障技术更像是一个钩子方法,它是在用户程序读取对象、创建新对象以及更新对象指针时执行的一段代码。Golang团队遵循上述两种不变式提到的原则,分别提出了两种实现机制:插入写屏障和 删除写屏障。
插入写屏障
规则:当一个对象引用另外一个对象时,将另外一个对象标记为灰色。
满足:强三色不变式。不会存在黑色对象引用白色对象
需要注意一点,插入屏障仅会在堆内存中生效,不对栈内存空间生效,这是因为go在并发运行时,大部分的操作都发生在栈上,函数调用会非常频繁。数十万goroutine的栈都进行屏障保护自然会有性能问题。 这也就是插入写屏障最大的弊端了,在一次正常的三色标记流程结束后,需要对栈上重新进行一次stw,防止对象误删除。
删除写屏障
规则:在删除引用时,如果被删除引用的对象自身为灰色或者白色,那么被标记为灰色。
满足:弱三色不变式,灰色对象到白色对象的路径不会断。
同样删除写屏障也有对应的劣势场景:一个对象的引用被删除后,即使没有其他存活的对象引用它,它仍然会活到下一轮。如此一来,会产生很多的冗余扫描成本,且降低了回收精度。
混合写屏障
核心定义:
- GC刚开始的时候,会将栈上的可达对象全部标记为黑色。
- GC期间任何在栈上新创建的对象均为黑色。
(上面两点只有一个目的,将栈上的可达对象全部标黑,最后无需对栈进行STW,就可以保证栈上的对象不会丢失。有人说,一直是黑色的对象,那么不就永远清除不掉了么,这里强调一下,标记为黑色的是可达对象,不可达的对象一直会是白色,直到最后被回收。)
- 堆上被删除的对象标记为灰色。
- 堆上新添加的对象标记为灰色。
增量和并发
上面介绍的是在数据层面如何避免变量被GC误清理,接下来介绍下Golang是如何提高GC执行性能的。
主要集中在两个技术:
- 增量垃圾收集 — 增量地标记和清除垃圾,降低应用程序暂停的最长时间
- 并发垃圾收集 — 利用多核的计算资源,在用户程序执行时并发标记和清除垃圾
增量式垃圾收集
增量式垃圾收集将整个垃圾收集过程分解为多个小步骤,每次执行一部分工作,而不是一次性完成整个垃圾收集过程。这样可以将垃圾收集的停顿时间分散到整个程序执行过程中,减少单次停顿的时间。
增量式垃圾收集的主要优点是:
- 减少了单次垃圾收集的停顿时间
- 提高了程序的响应性
- 使垃圾收集的工作负载更加平均
并发垃圾收集
Go 语言在 v1.5 中引入了并发的垃圾收集器,该垃圾收集器使用了我们上面提到的三色抽象和写屏障技术保证垃圾收集器执行的正确性。
并发垃圾收集允许垃圾收集器与用户程序同时运行,只在必要的时刻短暂暂停用户程序。Go语言的垃圾收集器采用了并发标记和并发清除的策略。

Go的并发垃圾收集过程大致如下:
- 初始标记阶段(STW):标记根对象,这个阶段需要暂停程序
- 并发标记阶段:垃圾收集器与用户程序并发执行,标记所有可达对象
- 重新标记阶段(STW):处理并发标记阶段中由于用户程序继续运行导致的对象引用变化
- 并发清除阶段:清除未标记的对象,与用户程序并发执行
GC触发时机
Go语言的垃圾收集器会在以下情况触发:
- 申请内存时根据堆大小判断是否达到上次GC后 × (1 + GOGC/100)
- 通过调用
runtime.GC()手动触发 - 系统定期触发(默认2分钟)
参考
垃圾收集器
一文弄懂 Golang GC、三色标记、混合写屏障机制
历史和演进
Go GC: Prioritizing low latency and simplicity
Getting to Go: The Journey of Go’s Garbage Collector