对于GC的介绍,可参考这篇我之前写过的文章,本文就直入正题直接介绍Lua的GC了。

机制介绍

  • Lua使用的是GC算法是标记清除(Mark Sweep)算法
  • 在lua5.1之前,lua采用了双色的标记清除算法,在这之后引入了三色标记清除算法
  • 标记清除算法不用像标记清楚算法每次赋值操作都要操作引用计数(对于动态语言效率有较大影响),也不会有环形引用的问题,但是由于标记清除在触发时,需要从root节点全量遍历被引用到的节点,通常是比较耗时的操作。

双色标记清除算法

每个新创建的对象颜色设置为白色
//初始化阶段
遍历root节点从白色置为黑色
//标记阶段
while(有新增的黑色节点)
{
    递归将黑色节点引用到的节点置黑
}
//清除阶段
遍历所有对象
if (为白色)
	没有被引用的对象,执行回收
else
	重新塞入到对象链表中,等待下一轮GC

三色标记清除算法

双色标记清除算法是一个不可中断的同步过程,非常容易造成CPU突刺。针对这个问题,lua从5.1以后便引入了增量GC的实现,将这样一整个同步的回收cycle,均摊到很多个可以增量执行的分步上,从而达到降低CPU突刺的目的,这就是三色标记清除算法。

每个新创建的对象颜色设置为白色
//初始化阶段
遍历root节点中引用的对象,从白色置为灰色,并且放入到灰色链表中
//标记阶段
while(灰色链表中还有未扫描的元素)
{
	从灰色链表中取出一个对象,将其置为黑色
	遍历这个对象关联的其他所有对象
    if(为白色)
        标记为灰色,加入到灰色链表中
}
//清除阶段
遍历所有对象
if(为白色)
	没有被引用的对象,执行回收
else
	重新塞入到对象链表中,等待下一轮GC

以一个增量垃圾回收器的视角来看,上层lua程序是GC状态的修改器,这对于垃圾回收器来说是一个比较讨厌的东西,因为程序运行在两个GC步骤之间,会修改GC的状态。

这种垃圾回收器与程序运行的关系对增量GC设计者提出了更高的要求,这是因为垃圾回收器再一次被唤醒时,相关的GC状态可能已经发生了变化:比如之前访问过的黑色节点,又引用到了一个新的白色节点,如何让垃圾回收器知道这件事情从而防止这个白色节点被错误的清除掉。这种情况在5.1之前的lua版本是不用考虑的,因为在之前版本下GC是一次执行到底的,中间不会插入应用程序的执行,从而不存在中间又被修改状态的情况。

资料参考/拓展阅读

.NET CLR之垃圾回收(GC)

Unity:GC机制(Mono Runtime GC)

Lua设计与实现--GC篇

lua的GC原理