浅谈Golang的garbage collcetion

104 阅读5分钟

先来谈谈传统的GC算法

1. 引用计数

根据对象自身的引用计数来回收,当引用计数为零时对象就会被回收。
优点:简单,回收速度快。
缺点:需要额外的空间存放计数,并且无法处理循环引用问题。

2. 标记清除

标记出所有不需要回收的对象,在标记完成之后统一回收掉所有未标记的对象。
优点:适用于可回收对象不多的场景
缺点:会造成不连续的内存空间,导致创建大对象时内存空间不连续而无法创建。

3. 复制法

将内存分为大小相同的两块,每次使用其中的一块,当这块内存使用完后,将当前状态下还存活的对象复制到另一块内存中,然后清除之前的内存块。
优点:很好的解决了内存碎片问题,每次需要复制存活的对象使得该算法效率低于标记清除法。
缺点:部分内存利用不到,造成资源的浪费,

4. 标记整理

标记过程同标记清除法,结束后将存活对象压缩至一端,然后清除边界的对象。
优点:有好的解决了内存碎片问题,也适用于存活对象多的场景。
缺点:性能低,在移动对象的时候不仅需要移动对象还需要维护对象的引用地址。

5. 分代GC(没记错的话Java就是用的这种方法)

将对象根据存活时间的长短进行分类,存活时间小于某个值的为年轻代,反之为老年代,永远不参与回收的是永久代。分代的思想主要就是年轻代的对象更倾向于被回收。

接下来时Golang的GC算法

使用无分代、不整理、并发的三色标记清除法。

  1. Go使用的是基于tcmalloc的现代内存分配算法,进行对象整理也不会有实质性的性能提升。
  2. Go的编译器会通过内存逃逸将大部分新生对象存储在栈上(栈上的对象会直接被回收),只有需要长期存在的对象才会分配到需要GC的堆空间中。
  3. Go的垃圾回收器和用户代码并行,使得STW的时间与对象的代数、对象的大小都没有关系。

V1.3

标记清除+STW,这个比较好理解,就不详细阐述了。

V1.5

提出了三色标记法,伴随有STW。

新生的对象都会标记成白色,然后从根节点出发一次遍历,将遍历过的对象标记成灰色的,并且全局变量和栈中的对象置灰色。最后将灰色对象标记为黑色,直到没有灰色对象全部转换成黑色对象为止。将所有的白色对象全部回收。

假设没有STW,在标记过程中很有可能出现引用关系的改变。一旦改变了这个关系(尤其是黑色对象直接指向白色对象),就会出现“误删除”的操作。

上述情况只针对于下面两个条件:

1. 一个白色对象被黑色对象引用(白色被挂在黑色下)  
2. 灰色对象与它之间的可达关系的白色对象遭到破坏(灰色同时丢了该白色) 如果当以上两个条件同时满足时,就会出现对象丢失现象。

STW也暴露出很大的性能瓶颈问题,针对上述问题提出了相应的解决方案。

引入了屏障机制

  1. 强三色不变式 不允许黑色对象引用白色对象
  2. 弱三色不变式 白色对象存在灰色对象的引用,或者可达它的链路的上游存在灰色对象,黑色对象就可以引用白色对象

插入屏障

  1. 在堆上的黑色对象引用白色对象时,需要把白色对象标记成灰色对象,满足强三色不变式
  2. 栈上的对象会在STW的保护下重新进行标记

删除屏障

  1. 被删除的对象,本身如果是灰色或者白色都会标记成灰色对象;
  2. 满足弱三色不变式

V1.8

针对插入写屏障和删除写屏障的短板:

插入写屏障:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活;
删除写屏障:回收精度低,GC开始时STW扫描堆栈来记录初始快照,这个过程会保护开始时刻的所有存活对象。

于是引入了混合写屏障 ,避免了对栈的re-scan操作,极大地减少了STW的时间。具体操作:

1、GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW),

2、GC期间,任何在栈上创建的新对象,均为黑色。

3、被删除的对象标记为灰色。

4、被添加的对象标记为灰色。

以上就是对Golang垃圾回收机制中的三色标记的详细描述,希望同学们批评指正。