GC全称为:garbage collection
,即垃圾回收,是一种自动管理内存的机制。
当程序向操作系统申请的内存不再被使用的时候,垃圾回收机制会将这个内存进行回收以便于下次申请内存时复用,或者归还给操作系统。这种内存级别资源的回收操作就是垃圾回收,而负责回收的程序组件就被叫做垃圾回收器。
传统的标记清除只有两种颜色,要么选中要么不选中。如果没选中就会被清除。
在此标记之前会出现STW,如果多次进行标记,那么就需要多次的STW,因此会造成频繁的暂停。下图将会介绍整个GC流程。
将所有的可达对象进行标记处理(Mark),什么是可达对象?就是通过系统根节点对象进行访问,能够被访问到的就是可达对象,如果访问不到,说明这个对象应当被回收再次利用!
stop the world
字面意思,所有程序暂停执行。因此程序必然会出现卡顿的情况。sweep
的过程中,可能有内存地址不连续的对象,将这些对象单独删除后,会产生大小不等的内存块。最严重的问题就是STW,因为STW是四个步骤进行时间的总和,每次暂停程序都会占用一定的时间,影响程序性能。
既然STW会影响到时间,增加消耗,那么我们能不能把这段暂停时间缩短甚至让他变为零呢?
我们能发现Sweep
也包含在了STW
中,而这段时间并不会影响程序的正常进行。因此可不可以以在第二个步骤进行之后就结束STW
呢?。
白色:未确定对象的集合。在回收开始阶段,所有对象均为白色,当回收结束后,白色对象均为不可达对象。
灰色:正在访问到的可达对象的集合,需要对其后继检查是否有可达对象,因此暂时标记为灰色。
黑色:已经被访问完成的可达对象的集合。
goroutine
都包含自己的执行栈,这些执行栈上包含栈的变量以及指向已经分配过的堆内存区块的指针。这一标记过程类似于广度优先遍历,逐层标记,并不是递归进行。并且这段过程并没有开启STW,那么会造成什么后果呢?
假设在重复第一步至第三步逐层访问的过程中,程序也在不断地运行。
当有一个灰色结点正在被访问的过程中,这个灰色结点有一个后继结点等待被下一轮访问。这时程序将这个指针给删除了,也就是说灰色结点不再指向这个后继结点。
此时程序又将一个黑色已经被访问完毕的结点指向了这个灰色结点的后继结点。当GC执行完毕之后所有白色结点将被回收。
那么问题也发生了,在访问过程中遗漏了经过更改的对象结点。
强三色不变式
弱三色不变式
只要出现强三色/弱三色不变式两个条件之一,就可以解决该问题。
对堆空间施行插入屏障。对栈进行STW保护。
当对象A引用B对象的时候,B对象直接设置为灰色,表示等待访问。也就是说B对象为新插入对象,只要插入扫描程序中,就直接被赋予为灰色。
这样操作满足了强三色不变式,黑色永远不会指向白色结点。
只有堆进行了插入屏障,而栈不会进行插入屏障。当GC结束后会重新开启STW单独对栈进行重新扫描,并且再次过程中不允许外界进行干扰。这仍然使用了STW,也降低了小部分性能。
当对象要被删除时,需要判断对象为什么颜色,如果对象为灰色或者白色,则全部标记为灰色。
那么这个对象这一轮不会被删除掉。但是指向它的那个指针在本轮已经被删除掉,那么他将会在下一轮被GC回收。
这样操作满足了弱三色不变式,保证灰色到白色这个路径不会断开
回收效率低,如果多次删除就会可能导致回收不成功,白白浪费一轮GC的回收时间。
执行操作:
融合了插入屏障和删除屏障的优点。栈不启用屏障、堆启用屏障保护。并且几乎不用开启STW保护,在对栈上的对象赋为黑色的时候还是需要停止goroutine
开启STW保护的。
以上便是Golang的GC全部的标记-清除逻辑及场景演示全过程。
GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。
GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通
GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW,效率较高。
垃圾回收器 | Go 程序员面试笔试宝典 (golang.design)
[Go三关-典藏版]Golang垃圾回收+混合写屏障GC全分析 - 知乎 (zhihu.com)