GC算法的原理大体就是:遍历系统中的所有对象,看哪些对象没有被引用,没有引用关系的就认为是可以回收的对象,可以删除 。
这里的关键在于,如何找出没有“引用”的对象。
使用引用计数的GC算法,会在一个对象被引用的情况下将该对象的引用计数加一 ,反之减一。 如果引用计数为0 ,那么就是没有引用的对象。 引用计数算法的优点是不需要扫描每个对象,对象本身的引用计数只需要减到0,就会被回收。 缺点是会有循环引用问题。
另一种算法是标记清除算法( Mark and Sweep )。 它的原理是每一次做GC的时候,首先扫描并且标记系统中 的所有对象,被扫描并且标记到的对象认为是可达的( reachable ),这些对象不会被回收;反之,没有被标记的对象认为是可以回收的 。 Lua采用的就是这种算法 。
早期的Lua 5.0使用的是双色标记清除算法( Two-Color Mark and Sweep ,如图 7-1所示),该算法的原理是 :系统中的每个对象非黑即白,也就是要么被引用,要么没有被引用 。 我们来简单看看这个算法的伪代码 :
每个新创建的对象的颜色为白色
// 初始化阶段
遍历root链表中的对象,并将其加入到对象链表中
//标记阶段
当对象链中还有未扫描的元素·
从中取出一个对象并将其标记为黑色
遍历这个对象关联的其他所有对象,标记为黑色
//回收阶段
追历所有对象如果为白色.
这些对象就是没有被引用的对象,逐个回收,否则 :
这些对象是被引用的对象 , 重新加入对象链表中等待下一轮的GC检查
双色标记清除算法
这个算法的缺陷在于,每个对象的状态是“二元”的,每个对象只可能有一种状态,不能有其他中间状态,这就要求这个算法每次做GC操作时不可被打断地一次性扫描并清除完所有对象。
下面来看着这个过程不能被打断的原因 。 如果在遍历对象链表时标记每个对象颜色的过程中被打断,此时新增了一个对象,那么应该将这个对象标记为白色还是黑色?如果标记为白色,假
如GC已经到了回收阶段,那么这个对象就会在没有遍历其关联对象的情况下被回收;如果标记
为黑色,假如GC已经到了回收阶段,那么这个对象在本轮GC中并没有被扫描过就认为是不必回
收的 。 可以看到,在双色标记清除算法中,标记阶段和回收阶段必须合在一起完成。
不能被打断,也就意味着每次GC操作的代价极大。 在GC过程中,程序必须暂停下来,不能进行其他操作 。
从Lua 5.1 开始,我们采用了在该算法的基础上改进的三色增量标记清除算法( Tri-Color
Incremental Mark and Sweep )。 与前面的算法相比,这个算法中每个对象的颜色多了一种(实际上,在Lua中是4种,后面再展开讨论)。 这样的好处在于:它不必再要求GC一次性扫描完所有的对象,这个GC过程可以是增量的,可以被中断再恢复并继续进行的 。 3种颜色的分类如下 。
白色: 当前对象为待访问状态,表示对象还没有被GC标记过,这也是任何一个对象创建后的初始状态。 换言之,如果一个对象在结束GC扫描过程后仍然是白色,则说明该对象没有被系统中的任何一个对象所引用,可以回收其空间了 。
灰色: 当前对象为待扫描状态,表示对象已经被GC访问过,但是该对象引用的其他对象还没有被访问到 。
黑色: 当前对象为已扫描状态,表示对象已经被GC访问过,并且该对象引用的其他对象也被访问过了 。
现在我们将这几个过程的操作和颜色的切换结合起来,如图所示 。
三色标记垃圾回收算法
下面给出 Lua 5.1 的GC算法的伪代码:
每个新创建的对象颜色为白色
//初始化阶段
遍历 root 节点中引用的对象,从白色置为灰色,并且放入到灰色节点链表中
//标记阶段
当灰色链表中还有未扫描的元素:
从中取出一个对象并将其标记为黑色遍历这个对象关联的其他所有对象.
如果是白色:
标记为灰色,加入灰色链表中
//回收阶段
遍历所有对象-如果为白色·
这些对象都是没有被引用的对象,逐个回收,否则:
重新加入对象链表中等待下一轮的 GC检查
可以看到,引入了灰色节点的概念后,算法不再要求一次性完整执行完毕,而是可以把已经扫描但是其引用的对象还未被扫描的对象置为灰色。 在标记阶段中,只要灰色节点集合中还有元素在,那么这个标记过程就会继续下去,即使中间被打断转而执行其他操作了,也没有关系 。
然而即使是这样,却仍然有另一个没有解决的问题。 从上面的算法可以看出,没有被引用的对象的颜色在扫描过程中始终保持不变,为白色 。 那么,假如一个对象在GC过程的标记阶段之后创建,根据前面对颜色的描述,它应该是白色的,这样在紧跟着的回收阶段,这个对象就会在没有被扫描标记的情况下被认为是没有被引用的对象而删除 。
因此, Lua的GC算法除了前面的三色概念之外,又细分出来一个“双白色”的概念 。 简单地说, Lua中的白色分为“当前白色”( currentwhite )和“非当前白色”( otherwhite )。 这两种白色的状态交替使用,第N次GC使用的是第一种白色,那么下一次就是另外一种,以此类推。
代码在回收时会做判断,如果某个对象的白色不是此次GC使用的白色状态,那么将不会认为是没有被引用的对象而回收,这样的白色对象将留在下一次GC中进行扫描,因为在下一次GC中上一次幸免的白色将成为这次的回收颜色。