GC:Garbage Collection(垃圾回收是编程语言中提供的自动的内存管理机制,自动释放不需要的内存对象,让出存储器资源。)GC过程中无需程序员手动执行,GC机制在现代很多编程语言都支持,GC能力的性能与优劣也是不同语言之间对比度指标之一。
关于Golang的GC在演进过程中经历了多次的变革。具体主要有如下几次重大改变:
- Go V1.3的标记-清除(mark and sweep)法。
- Go V1.5的三色并发标记法。
- Go V1.5的强三色不变式、弱三色不变式、插入屏障、删除屏障。
- Go V1.8的混合写屏障机制。
下面我们单独来讲一下Golang GC的几次重大改变。
Go V1.3的标记-清除(mark and sweep)法。
Go V1.3之前标记-清除算法核心步骤:
- 启动STW
- Mark需要清除的内存对象
- Sweep清除需要清除的对象
- 停止STW
Go V1.3标记-清除算法核心步骤:
- 启动STW
- Mark需要清除的内存对象
- 停止STW
- Sweep清除需要清除的对象
V1.3之前标记-清除算法详细过程:
- 第一步,暂停程序业务逻辑, 分类出可达和不可达的对象,然后做上标记。
- 第二步, 开始标记,程序找出它所有可达的对象,并做上标记。
- 第三步, 标记完了之后,然后开始清除未标记的对象。
- 第四步, 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。
如上图,目前程序的可达对象有1-2-3,4-7,stw之后标记。操作非常简单,但是有一点需要额外注意:mark and sweep算法在执行的时候,需要程序暂停!即 STW(stop the world),STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程的影响很大,所以STW也是一些回收机制最大的难题和希望优化的点。所以在执行第三步的这段时间,程序会暂定停止任何工作,卡在那等待回收执行完毕。
标记-清除(mark and sweep)的缺点:
- STW,stop the world;让程序暂停,程序出现卡顿
- 标记需要扫描整个heap
- 清除数据会产生heap碎片
Go V1.5的三色并发标记法。
Go V1.5的三色并发标记法的核心步骤:
- 启动STW
- 新创建的对象默认颜色是白色
- GC回收从根节点一次遍历所有对象,把遍历到的对象从白色集合放入灰色集合。
- 循环遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合,直到灰色中无任何对象
- 停止STW
- 回收所有的白色标记表的对象.也就是回收垃圾。
Go V1.5的三色并发标记法的详细过程:
第一步 , 每次新创建的对象,默认的颜色都是标记为“白色”,如图所示。
上图所示,我们的程序可抵达的内存对象关系如左图所示,右边的标记表,是用来记录目前每个对象的标记颜色分类。这里面需要注意的是,所谓“程序”,则是一些对象的根节点集合。所以我们如果将“程序”展开,会得到类似如下的表现形式,如图所示。
第二步, 每次GC回收开始, 会从根节点开始遍历所有对象,把遍历到的对象从白色集合放入“灰色”集合如图所示。
这里 要注意的是,本次遍历是一次遍历,非递归形式,是从程序抽次可抵达的对象遍历一层,如上图所示,当前可抵达的对象是对象1和对象4,那么自然本轮遍历结束,对象1和对象4就会被标记为灰色,灰色标记表就会多出这两个对象。
第三步, 遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合,如图所示。
这一次遍历是只扫描灰色对象,将灰色对象的第一层遍历可抵达的对象由白色变为灰色,如:对象2、对象7. 而之前的灰色对象1和对象4则会被标记为黑色,同时由灰色标记表移动到黑色标记表中。
第四步, 重复第三步, 直到灰色中无任何对象,如图所示。
当我们全部的可达对象都遍历完后,灰色标记表将不再存在灰色对象,目前全部内存的数据只有两种颜色,黑色和白色。那么黑色对象就是我们程序逻辑可达(需要的)对象,这些数据是目前支撑程序正常业务运行的,是合法的有用数据,不可删除,白色的对象是全部不可达对象,目前程序逻辑并不依赖他们,那么白色对象就是内存中目前的垃圾数据,需要被清除。
第五步: 回收所有的白色标记表的对象. 也就是回收垃圾,如图所示。
以上我们将全部的白色对象进行删除回收,剩下的就是全部依赖的黑色对象。
以上便是三色并发标记法,不难看出,我们上面已经清楚的体现三色的特性。但是这里面可能会有很多并发流程均会被扫描,执行并发流程的内存可能相互依赖,为了在GC过程中保证数据的安全,我们在开始三色标记之前就会加上STW,在扫描确定黑白对象之后再放开STW。但是很明显这样的GC扫描的性能实在是太低了。
Go V1.5的强三色不变式、弱三色不变式、插入屏障、删除屏障。(这个是对三色标记法的优化,我这里就不做详细讲述。)
Go V1.8的混合写屏障机制。
Go V1.8的混合写屏障机制核心步骤:
- 扫描栈中内存对象全部标记黑色,对新增加的对象标记为黑色
- 扫描堆中的全部对象,把遍历到的对象从白色集合放入灰色集合。循环遍历灰色集合,将灰色对象引用的对象从白色集合放入灰色集合,之后将此灰色对象放入黑色集合,直到灰色中无任何对象,对被删除的对象标记为灰色,对被添加的对象标记为灰色。
- 回收所有的白色标记表的对象.也就是回收垃圾。
关于混合写屏障,我们具体就场景进行分析。
GC开始:扫描栈区,将可达对象全部标记为黑
场景一:对象被一个堆对象删除引用,成为栈对象的下游
场景二:对象被一个栈对象删除引用,成为另一个栈对象的下游
场景三:对象被一个堆对象删除引用,成为另一个堆对象的下游
场景四:对象从一个栈对象删除引用,成为另一个堆对象的下游
混合写屏障的具体核心规则如下:
1. GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW),
2. GC期间,任何在栈上创建的新对象,均为黑色。
3. 被删除的对象标记为灰色。
4. 被添加的对象标记为灰色。
总结:
GoV1.8三色标记法加混合写屏障机制,栈空间不启动屏障机制,堆空间启动屏障机制。整个过程几乎不需要STW,效率较高。