上篇已经提到表作为lua中的一种对象,其生命周期是被GC管理的.
下面就从一个表的创建来分析GC模块是如何接管其生命周期的.
先来看一段lua代码
--a.lua
local a = {}
a.foo = "bar"
使用`luac -p -l a.lua` 得到如下结果
main <a.lua:0,0> (3 instructions at 0x20d7a50)
0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 0 functions
1 [1] NEWTABLE 0 0 0
2 [2] SETTABLE 0 -1 -2 ; "foo" "bar"
3 [2] RETURN 0 1
NEWTABLE对就OPCODE中的OP_NEWTABLE.
根据lopcodes.h中的定义, OP_NEWTABLE使用A,B,C三个寄存器,其中寄存器A用来接收创建的table, 寄存器B和C用于为新创建的table预分配array部分大小和hash部分大小.
在lvm.c中找到代码片段刚好印证了上面的解释.
vmcase(OP_NEWTABLE) {
int b = GETARG_B(i);
int c = GETARG_C(i);
Table *t = luaH_new(L);
sethvalue(L, ra, t);
if (b != 0 || c != 0)
luaH_resize(L, t, luaO_fb2int(b), luaO_fb2int(c));
checkGC(L, ra + 1);
vmbreak;
}
从luaH_new的定义可以得到一个结论,所有需要GC管理的对象都会调用luaC_newobj函数来分配内存空间.
//ltable.c
Table *luaH_new (lua_State *L) {
GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table));
Table *t = gco2t(o);
t->metatable = NULL;
t->flags = cast_byte(~0);
t->array = NULL;
t->sizearray = 0;
setnodevector(L, t, 0);
return t;
}
在luaC_newobj函数中, 首先调用luaM_newobject来为对象分配内存, 然后为正确管理此对象生命周期做了一些初始化操作.
1. 将对象标记为白色(lua5.3中使用的是三色标记清除垃圾回收算法)
2. 为此对象标记类型(类型的含义与上篇tt_字段的类型含义相同)
3. 将其挂在global_State上的allgc链表上.
由此可以得出一个结论,所有需要GC管理生命周期的对象都在allgc的链表上
//lgc.c
Object *luaC_newobj (lua_State *L, int tt, size_t sz) {
global_State *g = G(L);
GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz));
o->marked = luaC_white(g);
o->tt = tt;
o->next = g->allgc;
g->allgc = o;
return o;
}
在luaM_newobject中就只做了两件事:
1. 调用g->frealloc进行分配内存,如果分配失败的话,执一次fullgc,再次尝试分配
2. 将新增使用内存量, 加入到global_State中的GCdebt字段中.
其中g->frealloc是在调用lua_newstate创建lua_State时指定的, 值得一提的是当lua_Alloc中的参数ptr为null时, osize参数为要new的对象的类型.
//lua.h
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
//lmem.h
#define luaM_newobject(L,tag,s) luaM_realloc_(L, NULL, tag, (s))
//lmem.c
void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
void *newblock;
global_State *g = G(L);
size_t realosize = (block) ? osize : 0;
lua_assert((realosize == 0) == (block == NULL));
#if defined(HARDMEMTESTS)
if (nsize > realosize && g->gcrunning)
luaC_fullgc(L, 1); /* force a GC whenever possible */
#endif
newblock = (*g->frealloc)(g->ud, block, osize, nsize);
if (newblock == NULL && nsize > 0) {
lua_assert(nsize > realosize); /* cannot fail when shrinking a block */
if (g->version) { /* is state fully built? */
luaC_fullgc(L, 1); /* try to free some memory... */
newblock = (*g->frealloc)(g->ud, block, osize, nsize); /* try again */
}
if (newblock == NULL)
luaD_throw(L, LUA_ERRMEM);
}
lua_assert((nsize == 0) == (newblock == NULL));
g->GCdebt = (g->GCdebt + nsize) - realosize;
return newblock;
}
OP_NEWTABLE的最后一步就是调用checkGC, 如果有未偿还的债务,就执行一次luaC_step.
//lvm.c
#define checkGC(L,c) \
{ luaC_condGC(L, L->top = (c), /* limit of live values */ \
Protect(L->top = ci->top)); /* restore top */ \
luai_threadyield(L); }
//lgc.h
#define luaC_condGC(L,pre,pos) \
{ if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \
condchangemem(L,pre,pos); }
Lua中的GC实现为’增量标记-清除回收器’, 每次调用luaC_step都会执行一次增加回收.
到目前为止, GC可以通过luaC_newobj和luaC_condGC来获得哪些对象需要被其管理生命周期, 并且什么时间触发增量GC操作.
后面开始分析luaC_step函数.