突然想学一下golang,于是就研究了一下GC,了解golang底层是怎么操作的
GC原理推荐大佬文档(变量都有对应的源码):https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-channel/#64-channel
Go 语言的垃圾收集器从诞生的第一天起就一直在演进,除了少数几个版本没有大更新之外,几乎每次发布的小版本都会提升垃圾收集的性能,而与性能一同提升的还有垃圾收集器代码的复杂度,本节将从 Go 语言 v1.0 版本开始分析垃圾收集器的演进过程。
Go V1.3 之前的标记清除(mark and sweep) Go V1.5 三⾊标记法v1.0 — 完全串行的标记和清除过程,需要暂停整个程序;
v1.1 — 在多核主机并行执行垃圾收集的标记和清除阶段11;
v1.3 — 运行时基于只有指针类型的值包含指针的假设增加了对栈内存的精确扫描支持,实现了真正精确的垃圾收集12;将 unsafe.Pointer 类型转换成整数类型的值认定为不合法的,可能会造成悬挂指针等严重问题;
v1.5 — 实现了基于三色标记清扫的并发垃圾收集器13;
大幅度降低垃圾收集的延迟从几百 ms 降低至 10ms 以下;
计算垃圾收集启动的合适时间并通过并发加速垃圾收集的过程;
v1.6 — 实现了去中心化的垃圾收集协调器;
基于显式的状态机使得任意 Goroutine 都能触发垃圾收集的状态迁移;
使用密集的位图替代空闲链表表示的堆内存,降低清除阶段的 CPU 占用;
v1.7 — 通过并行栈收缩将垃圾收集的时间缩短至 2ms 以内;
v1.8 — 使用混合写屏障将垃圾收集的时间缩短至 0.5ms 以内;
v1.9 — 彻底移除暂停程序的重新扫描栈的过程;
v1.10 — 更新了垃圾收集调频器(Pacer)的实现,分离软硬堆大小的目标;
v1.12 — 使用新的标记终止算法简化垃圾收集器的几个阶段;
v1.13 — 通过新的 Scavenger 解决瞬时内存占用过高的应用程序向操作系统归还内存的问题;
v1.14 — 使用全新的页分配器优化内存分配的速度;
- 白色对象 — 潜在的垃圾,其内存可能会被垃圾收集器回收;
- 黑色对象 — 活跃的对象,包括不存在任何引用外部指针的对象以及从根对象可达的对象;
- 灰色对象 — 活跃的对象,因为存在指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象;
步骤:
- 第⼀步 , 就是只要是新创建的对象,默认的颜⾊都是标记为“⽩⾊”.
- 第⼆步, 每次GC回收开始, 然后从根节点开始遍历所有对象,把遍历到的对象从⽩⾊集合放⼊“灰⾊”集合
- 第三步, 遍历灰⾊集合,将灰⾊对象引⽤的对象从⽩⾊集合放⼊灰⾊集合,之后将此灰⾊对象放⼊⿊⾊集合
- 第四步, 重复第三步, 直到灰⾊中⽆任何对象
- 第五步: 回收所有的⽩⾊标记表的对象. 也就是回收垃圾.
改进之处:
- 强三色不变性 — 黑色对象不会指向白色对象,只会指向灰色对象或者黑色对象;(不可以黑接白)
- 弱三色不变性 — 黑色对象指向的白色对象必须包含一条从灰色对象经由多个白色对象的可达路径(黑接白的情况,一定要有灰色在路径中,可以另开)
内存屏障技术是一种屏障指令,它可以让 CPU 或者编译器在执行内存相关操作时遵循特定的约束,目前多数的现代处理器都会乱序执行指令以最大化性能,但是该技术能够保证内存操作的顺序性,在内存屏障前执行的操作一定会先于内存屏障后执行的操作。
Go V1.8 三⾊标记法,混合写屏障机制栈空间不启动,堆空间启动, 整体过程⼏乎不需要STW
具体操作:
- 1、GC开始将栈上的对象全部扫描并标记为⿊⾊(之后不再进⾏第⼆次重复扫描,⽆需STW)
- 2、GC期间,任何在栈上创建的新对象,均为⿊⾊。(并发)
- 3、被删除的对象标记为灰⾊。(并发)
- 4、被添加的对象标记为灰⾊。(并发)
- 满⾜: 变形的弱三⾊不变式. (结合了插⼊、删除写屏障两者的有点)
在介绍垃圾收集器的演进过程之前,我们需要初步了解最新垃圾收集器的执行周期,这对我们了解其全局的设计会有比较大的帮助。Go 语言的垃圾收集可以分成清除终止、标记、标记终止和清除四个不同阶段,它们分别完成了不同的工作:
图 7-35 垃圾收集的多个阶段
1.清理终止阶段;
- 暂停程序,所有的处理器在这时会进入安全点(Safe point);
- 如果当前垃圾收集循环是强制触发的,我们还需要处理还未被清理的内存管理单元;
2.标记阶段;
- 将状态切换至 _GCmark、开启写屏障、用户程序协助(Mutator Assists)并将根对象入队;
- 恢复执行程序,标记进程和用于协助的用户程序会开始并发标记内存中的对象,写屏障会将被覆盖的指针和新指针都标记成灰色,而所有新创建的对象都会被直接标记成黑色;
- 开始扫描根对象,包括所有 Goroutine 的栈、全局对象以及不在堆中的运行时数据结构,扫描 Goroutine 栈期间会暂停当前处理器;
- 依次处理灰色队列中的对象,将对象标记成黑色并将它们指向的对象标记成灰色;
- 使用分布式的终止算法检查剩余的工作,发现标记阶段完成后进入标记终止阶段;
3 标记终止阶段;
- 暂停程序、将状态切换至 _GCmarktermination 并关闭辅助标记的用户程序;
- 清理处理器上的线程缓存;
4.清理阶段;
- 将状态切换至 _GCoff 开始清理阶段,初始化清理状态并关闭写屏障;
- 恢复用户程序,所有新创建的对象会标记成白色;
- 后台并发清理所有的内存管理单元,当 Goroutine 申请新的内存管理单元时就会触发清理;
运行时虽然只会使用 _GCoff、_GCmark 和 _GCmarktermination 三个状态表示垃圾收集的全部阶段,但是在实现上却复杂很多,本节将按照垃圾收集的不同阶段详细分析其实现原理。
Go V1.3 普通的标记清除法, 整体过程需要STW,效率极低
Go V1.5 三⾊标记法, 对空间启动写屏障,栈空间不启动, 全部扫描之后,需要重新扫描⼀次栈(需要STW), 效率普通
Go V1.8 三⾊标记法,混合写屏障机制, 栈空间不启动,堆空间启动, 整体过程⼏乎不需要STW, 效率较高
Go 语言为了实现高性能的并发垃圾收集器,使用三色抽象、并发增量回收、混合写屏障、调步算法以及用户程序协助等机制将垃圾收集的暂停时间优化至毫秒级以下



