注意:5.1之后就開始使用增量式的收集器,也就是說它是隔行掃描的方式與解釋器一起工作。而5.1之前,收集器的運行會暫停對整個程序的響應。這一個改變還是非常有用處的!
1. gc的狀態(tài)階段
gc被分為下面五個階段:標記、整理、清掃字符串、清掃、收尾
狀態(tài)值的大小代表了它們的執(zhí)行順序,越小越先執(zhí)行。
#define GCSpause 0
#define GCSpropagate 1
#define GCSsweepstring 2
#define GCSsweep 3
#define GCSfinalize 4
2. GCSpause
GCSpause只是用來標記系統(tǒng)的根節(jié)點

Lua認為每個需要被GC管理的對象都有顏色,一開始,所有節(jié)點都是白色的。在我們標記階段,將節(jié)點逐個被設置為黑色。有一些節(jié)點因為還存在子節(jié)點還沒有處理,所以為灰色。在下面的源碼中我們可以看出,根節(jié)點含有其他子節(jié)點,所以根節(jié)點我們先將它設置為灰色。

在上面的源碼中我們還發(fā)現(xiàn)了markobject這個方法,點進去看一下它到底是如何標記的,它調用了另外一個函數(shù)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是將它本身及它的元表和環(huán)境表標為黑色(這是一個遞歸的過程,不斷去找尋它的元表)
- upValue也就是自由變量,當它脫離了它的作用域之后就會被設置為黑色,這里大家可能會對 if (uv->v == &uv->u.value) 產生疑惑,沒關系,我們再深入研究一下UpValue的概念以及它的新建與關閉。
- 其他都是函數(shù)、表、線程。它們就是通過對象保存的上一階段的全局灰鏈,然后將自身賦予灰鏈,并且標記自己為黑色,這樣就能把強引用的對象連接起來,在擴散標記階段,處理標記灰鏈中對象的時候,處理完子引用后,就可以通過對象的gclist指針恢復上一個灰鏈。 (?不是很理解)
Lua引用的實現(xiàn)
通過為每個變量至少創(chuàng)建一個upvalue 并按所需情況進行重復利用,保證了未脫離作用域的局部變量可以在閉包里正確使用。為了保證它的唯一性,Lua為整個運行棧保存了一個鏈接著所有正打開(正指向棧內的局部變量)的upValue的鏈表。所以當Lua在創(chuàng)建一個新的閉包的時候,就會遍歷外面的所有局部變量,對于這些變量,如果在鏈表里有找到它,就重用。否則創(chuàng)建一個新的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);
///發(fā)現(xiàn)已存在。
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是否為關閉,如果為關閉就將它標記為黑色。未關閉表示現(xiàn)在局部變量還沒有出作用域!所以不能標記為黑!

這里有兩個白色標記位。在GC標記結束但是清理流程尚未作完前,一旦對象間的關系產生了變化,比如有新增對象的時候,我們不知道它的生命期,那么我們就不能夠將它簡單的設置為黑色,所以這時候就引出了一個第二種白色的概念

GCSpause 階段執(zhí)行完,立刻就將狀態(tài)切換到了 GCSpropagate ,我們下篇再來深入講解GCSpropagate