11.垃圾回收
# 01.设计原理
- 传统的系统级编程语言(主要指C/C++)中,
开发人员需要自己申请释放内存
- 后来新语言(java,python,php等等)都引入了语言层面的
自动内存管理
内存释放由虚拟机(virtual machine)或运行时(runtime)来自动进行管理
- 而这种对不再使用的内存资源进行
自动回收的行为就被称为垃圾回收
# 1、标记-清除
它分为两个阶段:
标记阶段
— 从根对象出发查找并标记堆中所有存活的对象清除阶段
— 遍历堆中的全部对象,回收未被标记的垃圾对象并将回收的内存加入空闲链表
对象之间通过引用(指针)连在一起,构成一个有向图
从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象
清除阶段中收集器会依次遍历堆中的所有对象,释放其中没有被标记
- 在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3,程序无法访问块4和5
- 第一步将标记块1,并记住块2和3以供稍后处理
- 第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记
- 扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5
# 2、三色标记
# 1)三色标记法介绍
- 三色标记法只是为了叙述方便而抽象出来的一种说法,实际上的对象是没有三色之分的
- 这里的三色,对应了垃圾回收过程中对象的三种状态:
1)灰色(可能指向其他白色)
:对象还在标记队列中等待已被回收器访问到的对象
,不会被回收- 但回收器需要对其中的一个或多个指针进行扫描,因为
他们可能还指向白色对象
2)黑色(不指向其他白色)
:对象已被标记,gcmarkBits
对应位为1
-- 该对象不会在本次 GC 中被回收已被回收器访问到的对象
,其中所有字段都已被扫描- 黑色对象中任何一个指针都
不可能直接指向白色对象
3)白色(要被清除的)
:对象未被标记,gcmarkBits
对应位为0
-- 该对象将会在本次 GC 中被清理
# 2)具体流程如下图
- 在垃圾收集器开始工作时,程序中不存在任何的黑色对象,垃圾收集的
根对象会被标记成灰色
(栈和全局数据) - 标记清除主要包含下面四步
① 把根对象标记为灰色(
根是栈和全局数据
)② 选一个灰色对象标记为黑色
③ 将黑色对象所有指向的对象都标记成灰色
④ 重复 2,3 步骤直到不存在灰色对象
# 3)STW 停止用户程序
因为用户程序可能在标记执行的过程中修改对象的指针
所以三色标记清除算法
本身是不可以并发或者增量执行的,它仍然需要 STW
- STW弊端
- golang 的垃圾回收算法属于 标记-清除,是需要 STW 的
- STW 就是 Stop The World 的意思,在 golang 中就是要停掉所有的 goroutine,专心进行垃圾回收,待垃圾回收结束后再恢复 goroutine
- 而 STW 时间的长短直接影响了应用的执行,如果时间过长,那将是灾难性的
- 其中
写屏障
和辅助 GC
就是两种优化垃圾回收的方法
1)写屏障(Write Barrier)
- 而写屏障就是让 goroutine 与 GC 同时运行的手段,虽然不能完全消除 STW,但是可以大大减少 STW 的时间
- 写屏障在 GC 的特定时间开启,开启后指针传递时会把指针标记,即本轮不回收,下次 GC 时再确定
2)辅助 GC(Mutator Assist)
- 为了防止内存分配过快,在 GC 执行过程中
- GC 过程中 mutator 线程会并发运行,而 mutator assist 机制会协助 GC 做一部分的工作
# 3、写屏障
- 写屏障的核心目的是
确保垃圾回收器不会错过对新创建或新引用对象的标记
,避免对象在回收时出现引用丢失
- 主要用于解决**
三色标记法
**中可能存在的问题,从而保证垃圾回收的正确性
# 1)STW解决的问题
标记过程需的要STW
,因为对象引用关系如果在标记阶段做了修改,会影响标记结果的正确性
- 例如下图(假设没有STW)
- 1)灰色对象B引用白色对象C(此时C尚未被扫描)
- 2)当遍历完A对象后,A变成黑色
- 如果有其他程序断开了B对C的引用
- 同时添加了A对C的引用(由于A是黑色,所以C会一直是白色,被回收)
# 2)屏障技术
1)强三色不变性
:黑色不引用白色对象
插入屏障
:插入新引用时,如果被引用对象时白色,将其强制变为灰色
- 缺点:实现复杂,开销较大,会对性能产生一定的影响
2)弱三色不变性
:允许黑色对象暂时引用白色对象
,但垃圾收集最终会修复这些状态
插入屏障
:插入新引用时
,无需立刻标记为灰色,最终标记阶段前,必须修复黑白引用
- 缺点:由于黑色对象允许指向白色对象,可能出现
误回收
问题- 如果 GC 在标记过程中漏掉了某个对象的引用路径
- 可能会错误地将这个白色对象标记为不可达,从而在清理阶段进行误回收
# 3)混合写屏障
Go1.8版本引入的混合写屏障:工作机制
为什么会出现混合写屏障?
- 强三色不变性,要求
黑色不引用白色对象
,每次都要更新对象颜色,频繁调整状态,性能差
- 弱三色不变性,
允许黑色对象暂时引用白色对象
,某些对象可能被误认为是垃圾
- 混合写屏障的出现是为了解决并发垃圾回收中的
性能与准确性之间的矛盾
混合写屏障是一种折中方式,将强、弱三色一致性的特点结合
- 在标记阶段的
初期采用弱一致性
,在标记阶段的后期转为强一致性
- 初期可以快速标记对象,降低写屏障的频繁触发,而在后期进行严格检查,确保没有对象被漏标
- 优点:混合写屏障 在
确保垃圾回收安全的同时
,将写屏障的开销最小化
- 缺点:实现复杂度增加,需要更细致的调优
为什么 弱三色不变性会出现误回收,但是 混合写屏障就不会 误回收?
- 混合写屏障避免了误回收风险,因为它在标记阶段后期使用强一致性
- 确保所有仍有可能被引用的对象被正确标记,从而避免了漏标和误回收
# 4、垃圾回收触发机制
1、内存分配量达到阈值
- 每次内存分配都会检查当前内存分配量是否达到阈值,如果达到阈值则触发 GC
阈值 = 上次 GC 内存分配量 * 内存增长率
- 内存增长率由环境变量
GOGC
控制,默认为 100,即每当内存扩大一倍时启动 GC
2、定时触发 GC
- 默认情况下,2 分钟触发一次 GC,该间隔由
src/runtime/proc.go
中的forcegcperiod
声明
- 默认情况下,2 分钟触发一次 GC,该间隔由
3、手动触发 GC
- 在代码中,可通过使用
runtime.GC()
手动触发 GC
- 在代码中,可通过使用
# 5、GC 优化建议
- 由上文可知,GC 性能是与对象数量有关的,对象越多 GC 性能越差,对程序的影响也越大
- 所以在开发中要尽量减少对象分配个数,采用对象复用、将小对象组合成大对象或采用小数据类型(如使用
int8
代替int
)等
上次更新: 2024/10/15 16:27:13