GC实现方式有哪些
GC (Garbage Collection)泛指垃圾回收机制,在高级编程语言中,实现自动化的内存管理(内存分配与回收)。
- 自动GC
可以使得开发者更关注业务代码,无需管理程序内存分配与回收,有效的提高开发效率。 - 手动GC
手动GC可以更精确的通过API管理内存,可以极致利用资源。
GC实现方式
主要有两类,包括 追踪(Tracing) 和 引用计数 (Reference Counting)
- 追踪式GC
从根对象出发,根据对象之间的引用关系一步步扫描整个堆。
根对象: GC在追踪过程中,最先检查的对象。
- 全局变量 (编译阶段就能确定,生命周期同整个应用程序)
- 执行栈 (协程独立,栈上变量以及持有的逃逸对象堆上的引用)
- 寄存器 (指针,堆内存区块等)
实现方式
- 标记清除(产生内存碎片)
- 标记整理
- 等块复制copy清理
- 分代
- 增量(组合多方式)
- 并发(gc+应用程序并行)
- 引用计数式
给每个对象维护一个引用计数器,当对象被引用时,计数器+1,反之-1, 若是引用计数器=0表示可以清理回收。
GO的GC实现方式
golang中的gc,无分代(没有代际之分),不整理(回收过程中不对对象进行移动整理),并发的三色标记清除算法。
此处GC的并发是指,某个阶段GC可以与用户代码并发执行。
为什么不使用分代GC
- 逃逸分析特性使得分代GC在GO中没有优势。(分代重点处理新生代对象,在GO中短期活跃对象在栈,随着执行栈回收而被处理掉,不太需要GC的干预。)
- GO中使用的tcmall算法基本没有内存碎片,无需GC对内存碎片进行整理。
- GO中的GC目标更着重于并发执行,分代在STW上没有明显优势,与对象存活时间,对象大小没有直接关系。
Go中的GC流程
STW (Stop The World) 是指在GC过程中,为了保证GC算法实现的正确性,防止内存动态分配调整等问题,不可避免需要停止赋值器进一步操作对象的过程,整个用户代码是被停止执行,STW的延迟影响程序并发性能。
三色标记法
在1.5版本引入三色标记法,通过三个阶段的标记来确定需要清除的对象。
重要的标记节点(MARK)和清除阶段(SWEEP)。
- 白色: 表示未被垃圾回收器访问到的对象。(初始分配内存对象都是白色)
- 灰色:表示已被垃圾回收器访问到的对象,但其子对象还未被访问到。
- 黑色: 表示已被垃圾回收器访问到的对象,并且其子对象也都被访问到。
- 从根节点开始遍历所有对象,将根对象放入“灰色”集合。
- 遍历灰色集合,开启新一轮遍历(一层),将灰色对象可直达的对象放入“灰色集合”;将上一轮灰色对象(已遍历)放入“黑色集合”。
- 重复遍历灰色集合,直到灰色集合为空。
Q: 如果三色标记过程中,不通过STW暂停用户程序会出现什么问题?
A: 在标记过程中,用户代码的动态执行,会影响标记对象的引用关系。会出现对象标记扫描不准确问题。
在标记阶段,在未扫描obj2的时候,obj1移除对obj2的引用,正常情况下obj2,obj3是要被回收;但是用户程序代码又赋值将已经标记为黑色的obj6对象引用指向obj2。obj6子对象在这轮标记过程中,不会扫描obj2, 此刻obj2为白色对象,会被GC回收掉,不符合预期。
Q: 如何确保在不会GC误杀情况下,减少STW的时间?
首先,先明确什么情况下,对象会被GC误杀或者丢失?
- 一个白色对象被黑色对象引用 (黑色对象子对象,引用对象不会参与GC扫描)
- 灰色对象同时失去了白色对象的可达关系。(导致白色对象不会GC扫描)
为解决问题,在1.5版本引入屏障机制,在满足以下两者任意一个条件时候,就能保证GC对象不丢失,不误杀。
- 强三色不变式
强制性不允许黑色对象引用白色对象。
- 弱三色不变式
允许黑色对象引用白色对象,但是必须满足一定的条件:
- 白色对象必须存在其他灰色对象对它的引用。
- 或者这个白色对象的链路上游存在灰色对象。
如何实现强弱三色不变式
引入屏障技术,包括插入屏障和混合写屏障。
屏障本质上是程序执行过程中增加额外的判断机制;满足一定条件就使用类似回调或者hook通知GC处理。
- 插入屏障(insertion barrier)
插入写屏障又称为增量更新屏障;插入到黑色对象后的对象会被保守的标记为非白色对象。
流程如下图:
- 当obj1,obj5从灰色对象后开始新一轮扫描,obj1和obj5会变成黑色。
- 黑色对象有新对象的引用,如下图的obj4,obj7,新增对象应该是白色,但是为了满足强三色不变式条件,需要插入屏障来将两者变成灰色处理。
- 在下一轮扫描过程成,对象obj4,obj7就会变成黑色对象。
- 删除屏障
删除屏障又称之为给予起始快照的屏障。
当一个白色或者灰色对象的引用被移除时,该对象被标记为灰色,满足弱三色不变式条件。
会多一些问题:
被解引用的对象,会从白色标记为灰色,假设该对象确实没使用,则多了一轮扫描。
但是好处是能尽量减少STW的时间。
- 混合写屏障
插入屏障和删除屏障在分别独立使用的时候,会存在一些问题:
- 为了提高栈空间性能,栈上不使用屏障机制,如果堆栈存在互相引用的情况,只使用插入屏障,栈空间需要STW,扫描一遍栈空间对象。
- 删除写屏障回收精度低,需要多一轮扫描回收对象。
在1.8 版本设计混合写屏障机制,可以避免堆栈的重新扫描,大大减少STW时间(<ms级别),同时结合了插入屏障和删除屏障的两者优势。(并发三色标记法)
混合写屏障的几个规则
- GC启动开始优先扫描栈上的对象,并将可达对象全部标记为黑色。(包括用户并发代码创建新对象,避免栈空间重复扫描)
- 当对象删除时候出触发删除写屏障,将删除的对象标记为灰色。
- 当对象新增时候,触发插入写屏障,将新增对象(正常是白色)标记为灰色。
栈上对象(都是黑色)的引用修改调整,不会有屏障技术应用,不用重新扫描栈上对象。
若是存在栈上对象引用堆上白色对象(未被扫描),则无需做操作;若是被引用堆上对象被解引用,触发删除屏障,白色对象变灰色对象,此刻栈上的引用也保持正确性。