Go —— GC(垃圾回收)
本博客为一课程笔记,方便快速回顾。此课程所讲的 GC 有针对栈空间,但大多数博客均称 golang GC 只针对堆,不针对栈。
一,GC 存在的意义
让人能更专注于业务的编写,而不用担心内存的回收问题。
二,GC 的发展阶段
1,goV1.3 之前:标记-清除(mark and sweep)
1.1 逻辑:
STW(stop the world)——> 标记程序可达对象 ——> GC 清理不可达对象 ——> 停止暂停,程序继续执行
// 循环上述过程,直到程序生命周期结束
1.2 缺点:
1) STW,程序暂停,出现卡顿;
2) 标记需要扫描整个 heap;
3) 清除数据会产生 heap 碎片。
/*堆栈区别:堆(heap)和栈(stack)有两个含义,一个是指抽象数据结构,另一个是指操作系统中的内存空间。前者它们实现和作用都有很大差异所以比较少被放在一起比较,后者则是同气连枝常常被“相提并论”。堆在操作系统中为按需申请、动态分配,由于内存中的空闲空间不是连续的,操作系统会根据应用程序提出的申请从堆中按照一定的算法找出可用内存标记后给程序使用;而操作系统中的栈则是程序运行时自动拥有的一小块内存,大小由编译器参数决定,用于存放局部变量或者函数调用栈的保存。它们的区别如果长篇大论会讲非常多的内容,简单总结起来就是:
堆:时效持久、全局可见、手动申请、手动释放
栈:时效临时、局部可见、自动申请、自动释放*/
2,goV1.5: 三色标记法
2.1 逻辑:
白 ——> 灰 ——> 黑:
1,所有创建的新对象都标记为“白色”;
2,每次 GC 开始,从根节点遍历所有对象,把遍历到对象从“白色”集合中放入“灰色”集合;
3,遍历灰色集合,将灰色集合中的对象引用的对象从白色变为灰色,再把此灰色对象放入黑色集合;
4,重复第三步,直到没有灰色节点;
5,回收所有白色标记的对象。
2.2 隐含问题:
如果三色标记法不被 STW 保护:
1,一个白色对象被黑色对象引用;
2,灰色对象与其可达的白色对象遭到破坏;
以上两条同时满足,就会出现对象丢失现象。
解决办法:
1)强三色不变式:
强制性的不允许黑色对象引用白色对象。(破坏上述第一条可能)
2)弱三色不变式:
黑色对象可以引用白色对象,但白色对象的链路上游必须存在有灰色对象
// 三色标记法中只要满足强/弱之一,就可保证对象不丢失。
2.3 屏障机制(实现强/弱三色不变式的方法):
1,插入屏障
对象被引用时触发的机制:
在 A 对象引用 B 对象时,把 B 标记为灰色(不管 B 之前是白色还是灰色)—— 强三色不变式。
// 伪码
添加下游对象(当前下游对象 slot,新下游对象 ptr){
// 1
标记灰色(ptr)
// 2
slot=ptr
}
场景一:A.添加下游对象(nil,B) // A 之前没有下游,新添加下游 B,B 标记为灰色
场景二:A.添加下游对象(C,B) // A 将下游 C 更换为 B,B 标记为灰色
// 不在栈上使用,为了保证栈的运行效率(栈本身空间小,且对速率有一定要求),那怎么保证栈空间没有无辜的对象对清除呢?
答:在按照插入屏障机制完成三色标记法后,堆空间里被引用的对象肯定都标为黑色了,在清除白色对象前对栈空间的对象调用一次 STW ,重新从头扫描标记,以防止中途引入的对象没有被标记为黑色,然后再清除白色对象。
所以插入屏障的不足:结束时还是需要一次 STW 来重新扫描栈,大约需要 10~100ms。
2,删除屏障
对象被删除时触发的机制:
把要被删除(被断开)的对象标记为灰色,不管它本身是灰色白色。
// 伪码
添加下游对象(当前下游对象 slot,新下游对象 ptr){
// 1
if (slot 是灰色 || slot 是白色){
标记灰色(slot)
}
// 2
slot=ptr
}
场景一:A.添加下游对象(B,nil) // A对象删除对B对象的引用。B 被 A 删除,被标记为灰色
场景二:A.添加下游对象(B,C) // A对象更换下游对象为 C。B 被 A 删除,被标记为灰色
不足:回收精度低
一个对象即使被删除了(不被引用了),仍可以活过这一轮,直到下一轮 GC 中被清理掉。
3,go v1.8:三色标记法+混合写屏障机制
3.1 逻辑:
1,GC 开始直接将栈上的全部可达对象扫描并标记黑色(之后就无需 STW 再二次重复扫描);
2,GC 期间,任何在栈上创建的新对象,均标为黑色;
3,堆上被删除对象标记为灰色;
4,堆上被添加的对象标记为灰色;
// 伪码
添加下游对象(当前下游对象 slot,新下游对象 ptr) {
// 1
标记灰色(slot)
// 2
标记灰色(ptr)
// 3
slot=ptr
}