对于内存的管理,是程序在应用的时候的必需知识点,《Lua设计与实现》中对Lua语言的GC原理做了一个详细的讲解,云风的blog也对其进行了详尽的讲解Lua GC 的源码剖析 系列
给出作者 codedump 在github上的lua源码链接:https://github.com/lichuang/Lua-5.1.4-codedump
这儿就继续做《Lua设计与实现》的阅读笔记,对Lua GC原理及其过程做一个详尽的讲解,由于篇幅较大,就一分为二,写上下篇来讲解整个过程。
一、GC的原理及其算法设计
不同的语言,对GC算法的设计不同,常见的GC算法是引用计数和Mark-Sweep算法, c#采用的是Mark-sweep && compact算法, Lua采用的是Mark-sweep算法,分开说一下:
引用计数算法:在一个对象被引用的情况下,将其引用计数加1,反之则减1,如果计数值为0,则在GC的时候回收,这个算法有个问题就是循环引用。
Mark-sweep算法:每次GC的时候,对所有对象进行一次扫描,如果该对象不存在引用,则被回收,反之则保存。
在Lua5.0及其更早的版本中,Lua的GC是一次性不可被打断的过程,使用的Mark算法是双色标记算法(Two color mark),这样系统中对象的非黑即白,要么被引用,要么不被引用,这会带来一个问题:在GC的过程中如果新加入对象,这时候新加入的对象无论怎么设置都会带来问题,如果设置为白色,则如果处于回收阶段,则该对象会在没有遍历其关联对象的情况下被回收;如果标记为黑色,那么没有被扫描就被标记为不可回收,是不正确的。
为了降低一次性回收带来的性能问题以及双色算法的问题,在Lua5.1后,Lua都采用分布回收以及三色增量标记清除算法(Tri-color incremental mark and sweep)
其基本的原理伪代码,参考书中原文为:
每个新创建的对象颜色设置为白色 //初始化阶段 遍历root节点中引用的对象,从白色置为灰色,并且放入到灰色节点列表中 //标记阶段 while(灰色链表中还有未扫描的元素): 从中取出一个对象,将其置为黑色 遍历这个对象关联的其他所有对象: if 为白色 标记为灰色,加入到灰色链表中(insert to the head) //回收阶段 遍历所有对象: if 为白色, 没有被引用的对象,执行回收 else 重新塞入到对象链表中,等待下一轮GC
二、GC的数据结构
分析Lua中对于需要GC的类型数据
#define iscollectable(o) (ttype(o) >= LUA_TSTRING)
都会有一个基本的定义CommonHeader,其定义为:
next: GCObject链表指针,该指针用来将所有的GC对象都链接在一个表中;
tt: 数据类型:nil, boolean, number, string...
marked: 标记字段,byte表示的字段颜色定义为
这儿特定解释一下为什么会有两种白色,前面提到,5.1后的Lua采用的是三色标记算法,其实质是四色标记算法,分为0型白色和1型白色,在GC回收的时候,会设置当前的白色为其中一种,详见globalstate中的currentwhite,这样在代码回收的时候,如果当前对象的白色不为currentwhite,则认为其不可回收,这样的对象需要等到下一次的GC才能决定是否回收,具体参看后面的,会有对应的应用。对于global_state的设计为:
具体的参数的作用,详见注释,就不在一一解释了
三、GC的流程
1、数据的创建
想要了解GC的过程,首先看看数据是怎么在创建的时候被链接到GC链表中的,主要分为三种数据的创建
1) 一般数据的创建 luaC_link
简单直接,直接insert to the head
2) upval的创建 luaC_linkupval
3) userdata(udata)的创建 luaS_newdata
2、开始GC
整个GC过程分为五个阶段,其定义为
其执行GC的函数为singlestep,来看第一步的操作:
进一步看看markroot的操作:
其实就是reset一遍相关的变量,然后标记mainthread, G表,registry表,然后切换到下一个标记阶段。
参看定义:
最后都要执行reallymarkobject函数(此处需要展示一下我的竖屏截图便利了:D):
基本的注释都解释了各个对象是如何的处理的,udata是不会引用其他类型的数据,所以一步到黑色,upvalue则根据是否为close来决定是否标记到黑色,open状态的upvalue变化较为频繁,需要在后面的remarkupvals中解决。
写到这儿,已经写很长了,为了阅读的便利,让我们下一篇文章继续GC的进程吧:D