上篇已经提到表作为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函数.