1、GO V1.3之前:标记清除法(Mark and sweep)
为防止GC扫描时内存变化引起的混乱就需要STW(Stop the World),即在GC时,先停止所有的goroutine,在进行垃圾回收,等待垃圾回收完毕在恢复所有停止的goroutine
标记清除法过程:
- 启动STW,暂停程序的业务逻辑,找出不可达和可达对象
- 将所有的可达对象进行标记
- 清除未被标记的对象
- 停止STW,程序继续执行,循环往复直到进程程序声明周期结束
缺点:
- STW需要停止程序,造成程序卡顿
- 做标记时需要扫描整个heap
- 清除数据会产生heap碎片
后期开发人员尝试将STW范围缩短,将第四步和第三步调换位置但是效果并不理想
2、GO V1.5:三色标记法
三色对应垃圾回收中的三种状态:
- 白色:对象未被标记,
gcmarkBits
对应位为0,该对象会在本次GC中被清理 - 灰色:对象还在标记队列中等待被标记
- 黑色:对象已被标记,
gcmarkBits
对应位为1,该对象不会在本次GC中被回收
三色标记法执行过程:
- 新创建的对象,默认标记为白色
- 每次GC回收开始,从根节点开始遍历所有对象,将遍历到的对象从白色改为灰色,仅执行一次
- 将灰色对象作为根节点开始遍历所有的白色对象,将遍历到的对象颜色由白色改为灰色,并将作为根节点的灰色对象改为黑色
- 循环第三步,直到所有的灰色对象的颜色都变为黑色
- 将剩余的白色对象全部清除
缺点:
- 若一个不被灰色对象可达的白色对象被一个黑色对象所引用,那么将会造成该白色对象丢失问题
三色标记的优化:满足下列不变性之一就可保证对象不丢失
- 强三色不变性
- 强制性不允许黑色对象引用白色对象
- 弱三色不变性
- 黑色对象可以引用白色对象,但是该白色对象必须有灰色对象对他的直接引用,或者是可达链路中有灰色对象
优化的实现:屏障(额外的判断机制)
-
写屏障(Write Barrier),即在GC的特定时间下开启,开启后指针传递时会把指针标记,被标记的指针在本次GC过程中不会被清理,等到下次GC时才会被清理(栈不触发)
-
目的:缩短STW的时间,让goroutine和GC同时运行
-
插入写屏障(栈不触发)
- 具体实现:在A对象引用B对象时,B对象会被强制标记为灰色
- 含义:满足强三色不变性,即被引用对象会被强制标记为灰色
- 缺点:结束时需要STW重新扫描栈,大约需要10-100ms
-
删除写屏障
- 含义:满足弱三色不变性,即被删除对象,若本身为灰色或者白色,会被标记为灰色
- 缺点:回收精度低,即一个对象即使被删除,最后一个指向该对象的指针也会等到下一次GC时才会被删除
3、GO V1.8:混合写屏障机制
混合写屏障
- 无需STW,GC在首次执行时,先将栈上的所有对象标记为黑色,之后不在进行第二次重复扫描
- GC在执行过程中,在栈上新创建的对象,默认被标记为黑色
- 将被删除的对象标记为灰色
- 将被添加的对象标记为灰色
优点:
- 满足弱三色不变性,结合了插入写屏障和删除写屏障的优点
4、GC触发方式
- 内存分配阈值
- 阈值 = 上次GC内存分配值 * 内存增长率
- 内存增长率由GOGC设置,默认为100,每次内存分配时,都会检测当前内存分配是否已经达到阈值,若达到就会触发GC,即每当内存分配将要增长一倍时就会触发GC
- 定时触发
src/runtime/proc.go
文件中的forcegcperiod
设定触发GC时间间隔,默认为2min
- 手动触发,调用
runtime.GC()
方法,触发GC
总结:
- 标记清除法:整体过程需要STW,效率低
- 三色标记法:堆空间启动写屏障,栈空间不启动,在扫描全部节点之后需要重新扫描一次栈(STW),效率普通
- 三色标记法+混合写屏障机制,栈空间不启动,堆空间启动,整体过程几乎不需要STW,效率较高