垃圾回收策略(Garbage Collection,GC)用于回收不再使用的内存,避免系统的内存被占满。Go1.3之前采用标记清除法, Go1.3之后采用三色标记法,Go1.8采用三色标记法+混合写屏障。
前置概念:
1)根对象:应用程序中可以直接或间接访问的对象,主要包括:
- 全局变量:在程序编译期间就能确定,全局变量存在于程序的整个生命周期
- 执行栈:Go语言中协程是分配在堆上,每个协程都含有自己的执行栈
- 寄存器:寄存器的值可能表示一个指针,这些指针可能指向某些赋值器分配的堆内存区
2)STW(Stop-The-World):系统在执行特定操作时,暂停所有应用程序线程,直到某个特定的事件发生或处理完成。
1. 标记清除法
具体步骤:
1)首先从根对象出发,标记所有可达对象。
这一过程通常使用深度优先搜索或广度优先搜索进⾏。标记的方式通常是将对象的标记位从未标记改为已标记。
2)在清扫阶段,遍历整个堆内存,将未被标记的对象视为垃圾,即不再被引用。所有未被标记的对象都将被回收,它们的内存将被释放,以便后续的内存分配。
标记清除算法执行完清扫阶段后,可能会产生内存碎片,即一些被回收的内存空间可能是不连续的。为了解决这个问题,一些实现中可能会进行内存碎片整理。
总结:
标记清除算法的主要优势是能够回收不再使用的内存,但它也有一些缺点,其中主要的缺点是清扫阶段可能会引起一定程度的停顿,因为在这个阶段需要遍历整个堆内存。另外,由于标记清除算法只关注“存活”和 “垃圾”两种状态,不涉及内存分配的具体位置,可能导致内存碎片的产生。
2. 三色标记法
三色标记是指将对象分为三种颜色:白色、灰色、和黑色。
(1)白色:代表最终需要清理的对象内存块
(2)灰色:待处理的内存块
(3)黑色:活跃的内存块
具体步骤:
1)初始时,所有对象都被标记为白色,表示它们都是未被访问的垃圾对象。
2)根搜索:从根对象开始搜索,所有根对象被标记为灰色,表示它们是待处理的对象。
3)标记阶段:从灰色对象开始,垃圾回收器遍历对象的引用关系,将其引用的对象标记为灰色,然后当前该对象标记为黑色。重复这一步骤,直到所有可达对象都被标记为黑色。
4)清扫阶段:在标记完成后,垃圾回收器会扫描堆中的所有对象,将未被标记的对象回收(释放其内存)。这些未被标记的对象被认为是不可达的垃圾。
5)内存返还:垃圾回收完成后,系统中的内存得以回收并用于新的对象分配。
总结:
1)优点:在标记阶段,垃圾回收器采用并发标记的方式,与程序的执行同时进行。这意味着程序的执行不会因为垃圾回收停顿,减小了对程序性能的影响。
2)缺点:如果对象的引用被用户修改了,那么之前的标记就⽆效了。
3)垃圾回收的触发条件通常是在分配新对象时,如果达到⼀定的内存分配阈值,就会触发垃圾回收。 另外,⼀些特定的事件(如系统调用、网络阻塞等)也可能触发垃圾回收。
3. 三色标记法+混合写屏障
屏障技术可以理解为一种回调机制,在程序的某种执行过程中加一个判断机制,满足判断机制则执行回调函数,类似于钩子函数(Hook)。
实际上,垃圾回收机制只用于回收堆上的内存,栈中的内存如局部变量、函数调用等会在调用结束后自动释放。也就是说,屏障机制只能作用于堆对象。屏障机制分为插入写屏障和删除写屏障:
- 插入写屏障实现了强三色不变性,给对象添加引用关系时触发
- 删除写屏障实现了弱三色不变性,删除对象引用关系时触发
- 强三色不变性强制性的不允许黑色对象引用白色对象;
- 弱三色不变性允许黑色对象引用白色对象,但是白色对象必须存在其他灰色对象对它的引用。
插入写屏障:每当一个对象被引用,就会触发判断:如果这次操作是一个白色对象被黑色对象引用,就把这个白色对象标记为灰色
删除写屏障:每当一个对象被删除时,就会触发判断:如果是一个灰色对象引用的白色对象被删除,那么就把这个白色对象标记为灰色
插入写屏障和删除写屏障都有各自的短板:
插入写屏障结束时需要 STW(标记清除)重新扫描栈
删除写屏障回收精度低,在开始时需要 STW 扫描整个堆栈记录初始快照
基于插入写屏障和删除写屏障在结束时需要STW来重新扫描栈,带来性能瓶颈。混合写屏障分为以下四步:
-
GC开始时,将栈上的全部对象标记为黑色(不需要二次扫描,无需STW);
-
GC期间,任何栈上创建的新对象均为黑色
-
被删除引用的对象标记为灰色
-
被添加引用的对象标记为灰色
因此,一次完整的GC分为四个阶段:
- 准备标记(需要STW),开启写屏障。
- 开始标记
- 标记结束(STW),关闭写屏障
- 清理(并发)
总结:
标记清除法需要一次全局 STW,先标记可达对象,再清除所有的不可达对象,性能很差;
三色标记法身无法在并发条件下正确执行,单独使用也需要 STW 才能保证正确性;
为了优化这一过程,避免全局 SWT,引入了插入写屏障,插入写屏障是一种触发机制,每当一个对象被引用时,如果是黑色对象引用白色对象,就把这个白色对象置灰,但是由于屏障机制无法作用到栈对象,为了避免与栈相关的活跃对象被错误回收,需要在三色标记法结束后,对栈做一次 STW,清除掉垃圾对象;插入写屏障虽然优化了全局 SWT,但是每次GC也需要对栈上的一次 STW;
为了解决这一问题,最终的方案是混合写屏障,混合写屏障的触发机制有两个:被删除的对象会被标记为灰色,被添加的对象会被标记为灰色;同时在开始时会把栈上所有可达对象置黑,栈上对象被创建时也会置黑。这样就记录了栈上的对象状态,避免了错误回收活跃对象;
混合写屏障结合了插入写屏障和删除写屏障的优点,避免了在插入写屏障结束后需要进行全栈扫描的性能问题,实现了更高效的垃圾回收机制。
参考:代码随想录、Go语言的垃圾回收机制,图文并茂 一篇搞懂!_go垃圾回收-CSDN博客