GC的原理
首先Lua是以union+type的形式來保存值的。如果是需要被GC管理的值,就以GCObject指針的形式保存,否則就直接存值。而所有的GCObject都有相同的CommonHeader,在CommonHeader中next域用來串聯單鏈表,tt用來識別類型,marked用來標記清除工作。源碼如下:
lobject.h
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
typedef struct GCheader {
CommonHeader;
} GCheader;
typedef union {
GCObject *gc;
void *p;
lua_Number n;
int b;
} Value;
實現時,並不是通過一條鏈表維護的。對於string來說,所有的string放在一張大的hash表中,以保證系統中不會有值相同的string被創建兩份。所以string是單獨管理的,不再GCObject的鏈表中。
還有其他特殊類型,如thread、userdata都會在GC時進行特殊處理。
GC分爲5個階段,在lgc.h中的singlestep方法中實現。GCSpause階段完成後會立刻切換GCSpropagate,GCSpropagate階段會分步完成,當檢測到有未標記的對象時,迭代進行標記。之後進GCSsweepstring階段,GCSsweepstring階段主要用於清理Lua中的string,這個階段中每一步會清理hash表的一列。之後是GCSsweep階段,和GCSsweepstring階段類似。最後是GCSfinalize階段,會在這個階段對有GC元方法的userdata對象調用它GC方法,並把userdata作爲參數傳入。源碼如下:
static l_mem singlestep (lua_State *L) {
global_State *g = G(L);
/*lua_checkmemory(L);*/
switch (g->gcstate) {
case GCSpause: {
markroot(L); /* start a new collection */
return 0;
}
case GCSpropagate: {
if (g->gray)
return propagatemark(g);
else { /* no more `gray' objects */
atomic(L); /* finish mark phase */
return 0;
}
}
case GCSsweepstring: {
lu_mem old = g->totalbytes;
sweepwholelist(L, &g->strt.hash[g->sweepstrgc++]);
if (g->sweepstrgc >= g->strt.size) /* nothing more to sweep? */
g->gcstate = GCSsweep; /* end sweep-string phase */
lua_assert(old >= g->totalbytes);
g->estimate -= old - g->totalbytes;
return GCSWEEPCOST;
}
case GCSsweep: {
lu_mem old = g->totalbytes;
g->sweepgc = sweeplist(L, g->sweepgc, GCSWEEPMAX);
if (*g->sweepgc == NULL) { /* nothing more to sweep? */
checkSizes(L);
g->gcstate = GCSfinalize; /* end sweep phase */
}
lua_assert(old >= g->totalbytes);
g->estimate -= old - g->totalbytes;
return GCSWEEPMAX*GCSWEEPCOST;
}
case GCSfinalize: {
if (g->tmudata) {
GCTM(L);
if (g->estimate > GCFINALIZECOST)
g->estimate -= GCFINALIZECOST;
return GCFINALIZECOST;
}
else {
g->gcstate = GCSpause; /* end collection */
g->gcdept = 0;
return 0;
}
}
default: lua_assert(0); return 0;
}
}
Lua中的GC管理對象的都會有一個顏色,所有對象一創建出來都是白色,是可以清理的,在標記階段會將簡單的對象設置爲不可清理的黑色,對於複雜的會關聯到其他對象的對象,在沒處理完其他對象時,會標記爲灰色,當清理階段結束之後會將所有對象設置爲白色,在下次GC時再重新設置。這裏面白色會有兩種:0和1。有兩個白色是因爲,Lua的GC是單遍掃描的,所以當標記流程結束,但是清理流程沒有結束時,當有新的對象產生時,由於無法預測如果直接標記爲黑色,可能會導致無法清除。而兩種白色就是一個開關,當前清理0白色時,則保護1白色,反之也是一樣。