注意:5.1之后就开始使用增量式的收集器,也就是说它是隔行扫描的方式与解释器一起工作。而5.1之前,收集器的运行会暂停对整个程序的响应。这一个改变还是非常有用处的!
1. gc的状态阶段
gc被分为下面五个阶段:标记、整理、清扫字符串、清扫、收尾
状态值的大小代表了它们的执行顺序,越小越先执行。
#define GCSpause 0
#define GCSpropagate 1
#define GCSsweepstring 2
#define GCSsweep 3
#define GCSfinalize 4
2. GCSpause
GCSpause只是用来标记系统的根节点
Lua认为每个需要被GC管理的对象都有颜色,一开始,所有节点都是白色的。在我们标记阶段,将节点逐个被设置为黑色。有一些节点因为还存在子节点还没有处理,所以为灰色。在下面的源码中我们可以看出,根节点含有其他子节点,所以根节点我们先将它设置为灰色。
在上面的源码中我们还发现了markobject这个方法,点进去看一下它到底是如何标记的,它调用了另外一个函数reallymarkobject:
static void reallymarkobject (global_State *g, GCObject *o) {
lua_assert(iswhite(o) && !isdead(g, o));
white2gray(o);
switch (o->gch.tt) {
case LUA_TSTRING: {
return;
}
case LUA_TUSERDATA: {
Table *mt = gco2u(o)->metatable;
gray2black(o); /* udata are never gray */
if (mt) markobject(g, mt);
markobject(g, gco2u(o)->env);
return;
}
case LUA_TUPVAL: {
UpVal *uv = gco2uv(o);
markvalue(g, uv->v);
if (uv->v == &uv->u.value) /* closed? */
gray2black(o); /* open upvalues are never black */
return;
}
case LUA_TFUNCTION: {
gco2cl(o)->c.gclist = g->gray;
g->gray = o;
break;
}
case LUA_TTABLE: {
gco2h(o)->gclist = g->gray;
g->gray = o;
break;
}
case LUA_TTHREAD: {
gco2th(o)->gclist = g->gray;
g->gray = o;
break;
}
case LUA_TPROTO: {
gco2p(o)->gclist = g->gray;
g->gray = o;
break;
}
default: lua_assert(0);
}
}
可以看出,字符串对象并没有任何的操作直接返回
- userdata是将它本身及它的元表和环境表标为黑色(这是一个递归的过程,不断去找寻它的元表)
- upValue也就是自由变量,当它脱离了它的作用域之后就会被设置为黑色,这里大家可能会对 if (uv->v == &uv->u.value) 产生疑惑,没关系,我们再深入研究一下UpValue的概念以及它的新建与关闭。
- 其他都是函数、表、线程。它们就是通过对象保存的上一阶段的全局灰链,然后将自身赋予灰链,并且标记自己为黑色,这样就能把强引用的对象连接起来,在扩散标记阶段,处理标记灰链中对象的时候,处理完子引用后,就可以通过对象的gclist指针恢复上一个灰链。 (?不是很理解)
Lua引用的实现
通过为每个变量至少创建一个upvalue 并按所需情况进行重复利用,保证了未脱离作用域的局部变量可以在闭包里正确使用。为了保证它的唯一性,Lua为整个运行栈保存了一个链接着所有正打开(正指向栈内的局部变量)的upValue的链表。所以当Lua在创建一个新的闭包的时候,就会遍历外面的所有局部变量,对于这些变量,如果在链表里有找到它,就重用。否则创建一个新的upValue并保存到链表中。
再了解一下upValue的结构(看注释即可明白)
typedef struct UpVal {
CommonHeader;
TValue *v; /* points to stack or to its own value */
union {
TValue value; /* the value (when closed) */
struct { /* double linked list (when open) */
struct UpVal *prev;
struct UpVal *next;
} l;
} u;
} UpVal;
如下我们可以看到,不断的遍历外部的局部变量,如果不存在upValue的就新建它,那么此时它就是不关闭的
UpVal *luaF_findupval (lua_State *L, StkId level) {
global_State *g = G(L);
///得到openupval链表
GCObject **pp = &L->openupval;
UpVal *p;
UpVal *uv;
///开始遍历open upvalue。
while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) {
lua_assert(p->v != &p->u.value);
///发现已存在。
if (p->v == level) {
if (isdead(g, obj2gco(p))) /* is it dead? */
changewhite(obj2gco(p)); /* ressurect it */
///直接返回
return p;
}
pp = &p->next;
}
///否则new一个新的upvalue
uv = luaM_new(L, UpVal); /* not found: create a new one */
uv->tt = LUA_TUPVAL;
uv->marked = luaC_white(g);
///设置值
uv->v = level; /* current value lives in the stack */
///首先插入到lua_state的openupval域
uv->next = *pp; /* chain it in the proper position */
*pp = obj2gco(uv);
///然后插入到global_State的uvhead(这个也就是双向链表的头)
uv->u.l.prev = &g->uvhead; /* double link it in `uvhead' list */
uv->u.l.next = g->uvhead.u.l.next;
uv->u.l.next->u.l.prev = uv;
g->uvhead.u.l.next = uv;
lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
return uv;
}
所以我们才在下面的if语句里判断upValue是否为关闭,如果为关闭就将它标记为黑色。未关闭表示现在局部变量还没有出作用域!所以不能标记为黑!
这里有两个白色标记位。在GC标记结束但是清理流程尚未作完前,一旦对象间的关系产生了变化,比如有新增对象的时候,我们不知道它的生命期,那么我们就不能够将它简单的设置为黑色,所以这时候就引出了一个第二种白色的概念
GCSpause 阶段执行完,立刻就将状态切换到了 GCSpropagate ,我们下篇再来深入讲解GCSpropagate