何为GC?
GC:Garbage Collection(垃圾回收)
垃圾指内存中不再使用的内存区域,自动发现与释放这种内存区域的过程就是垃圾回收。
引用计数
标记-清除
分代收集
为什么要有GC?
程序运行过程中会申请大量的内存空间,但内存资源是有限的,而对于一些无用的内存空间如果不及时清理的话会导致内存使用殆尽(内存溢出),导致程序崩溃,因此管理内存是一件重要且繁杂的事情
而垃圾回收可以让内存重复使用,并且减轻开发者对内存管理的负担,减少程序中的内存问题。
Go垃圾回收发展史
版本 | 发布时间 | GC算法 | STW时间 | 重大更新 |
---|---|---|---|---|
V1.1 | 2013/5 | STW | 可能秒级别 | |
V1.3 | 2014/6 | Mark和Sweep分离,Mark、STW、Sweep并发 | 百ms级别 | |
V1.4 | 2014/12 | runtime代码基本都由C和少量汇编改为Go和少量汇编, 包括GC部分, 以此实现了准确式GC,减少了堆大小, 同时对指针的写入引入了write barrier, 为1.5铺垫 | 百ms级别 | |
V1.5 | 2015/8 | 三色标记法,并发Mark,并发Sweep。非分代、非移动、并发的收集器 | 10ms-40ms级别 | 重要更新版本,生产上GC基本不会成为问题 |
V1.6 | 2016/2 | 1.5中一些与并发GC不协调的地方更改。集中式的GC协调协程,改为状态机实现 | 5-20ms | |
V1.7 | 2016/8 | GC时栈收缩改为并发,span中对象分配状态由freelist改为bitmap | 1-3ms左右 | |
V1.8 | 2017/2 | hybird write barrier,消除了STW中的重新扫描栈 | sub ms | Golang GC进入Sub ms时代 |
golang gc 触发条件
GC调用方式 | 所在位置 | 代码 |
---|---|---|
定时调用 | runtime/proc.go:forcegchelper() | gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()}) |
分配内存时调用 | runtime/malloc.go:mallocgc() | gcTrigger{kind: gcTriggerHeap} |
手动调用 | runtime/mgc.go:GC() | gcStart(gcTrigger{kind: gcTriggerCycle, n: n + 1}) |
STW(stop the world)
STW的过程中,CPU不执行用户代码,全部用于垃圾回收
Root对象
根对象是mutator不需要通过其他对象就可以直接访问到的对象. 比如全局对象, 栈对象, 寄存器中的数据等. 通过Root对象, 可以追踪到其他存活的对象.
可达性
即通过对Root对象能够直接或者间接访问到.
Mark(标记)
GC 开始,从 root 开始一层层扫描,扫描过程中把能被触达的 object 标记出来,那么堆空间未被标记的 object 就是垃圾了
Sweep(清除)
遍历堆空间所有 object 对未标记的 object 进行清除,清除完成则表示 GC 完成。
golang gc 演变过程
Go 1.1 GC过程
Go 1.3 GC过程
Go 1.5 GC过程
step1:step2:step3:step4:step5:
三色法动态图
1. 正常情况下,写操作就是正常的赋值。
2. GC 开始,开启写屏障等准备工作。开启写屏障等准备工作需要短暂的 STW。
3. Stack scan 阶段,从全局空间和 goroutine 栈空间上收集变量。
4. Mark 阶段,执行上述的三色标记法,直到没有灰色对象。
5. Mark termination 阶段,开启 STW,回头重新扫描 root 区域新变量,对他们进行标记。
6. Sweep 阶段,关闭 STW 和 写屏障,对白色对象进行清除。
写屏障(write barrier)
黑色对象不会指向白色对象,如果被指向了就会出现被清除的白色对象,实际是被引用的对象,造成错误的清理
写屏障的实现有很多模式,在golang1.7之前主要采用的是Dijkstra-style insertion write barrier,其伪码实现如下:
writePointer(slot, ptr):
shade(ptr)
*slot = ptr
黑色对象不会指向白色对象
“强-弱” 三色不变式
- 强三色不变式
不存在黑色对象引用到白色对象的指针
- 弱三色不变式
所有被黑色对象引用的白色对象都处于灰色保护状态
为了遵循上述的两个方式,Golang团队初步得到了如下具体的两种屏障方式“插入屏障”, “删除屏障”。
Go 1.8 GC过程
三色标记方式,需要在最后重新扫描一下所有全局变量和 goroutine 栈空间,如果系统的 goroutine 很多,这个阶段耗时也会比较长,甚至会长达 100ms。毕竟 Goroutine 很轻量,大型系统中,上百万的 Goroutine 也是常有的事儿。
Go 在1.8 版本使用混合写屏障(hybrid write barrier)机制,将第一次短暂的 STW取消了,在1.9 版本又大大减少了第二次的STW。
大致如下图所示:
GC开始,默认都是白色
扫描栈区,将可达对象全部标记为黑
Golang中的混合写屏障满足弱三色不变式,结合了删除写屏障和插入写屏障的优点,只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间