这次主要来分析lua的gc。
首先lua中的数据类型包括下面9种,ni, Boolean, number, string, table,user data, thread , functions 以及 lightusedata.其中 string, table,thread , function 是会被垃圾回收管理的,其他的都是值存在。
因此我们来看对应的GC数据结构.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #define CommonHeader GCObject *next; lu_byte tt; lu_byte marked typedef struct GCheader { CommonHeader; } GCheader; union GCObject { GCheader gch; union TString ts; union Udata u; union Closure cl; struct Table h; struct Proto p; struct UpVal uv; struct lua_State th; /\* thread \*/ };
我们可以看到在lua中字符串,userdata, thread, table ,string, thread(以及Upval, proto) 都会被垃圾回收管理。这里比较关键的就是GCheader这个结构体,我们可以看到这个结构体其实就是一个链表,也就是说所有的gc对象都会被链到一个链表中,其中tt表示当前对象的类型,在lua中包括下面这些类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define LUA_TNIL 0 #define LUA_TBOOLEAN 1 #define LUA_TLIGHTUSERDATA 2 #define LUA_TNUMBER 3 #define LUA_TSTRING 4 #define LUA_TTABLE 5 #define LUA_TFUNCTION 6 #define LUA_TUSERDATA 7 #define LUA_TTHREAD 8
而marked表示当前对象的状态(涉及到gc算法,后续会详细分析),状态位包括下面这些:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define WHITE0BIT 0 #define WHITE1BIT 1 #define BLACKBIT 2 #define FINALIZEDBIT 3 #define KEYWEAKBIT 3 #define VALUEWEAKBIT 4 #define FIXEDBIT 5 #define SFIXEDBIT 6 #define WHITEBITS bit2mask(WHITE0BIT, WHITE1BIT)
然后我们来看lua_state这个数据结构,这个结构也就是一个lua意义上的thread。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 struct lua_State { CommonHeader; lu_byte status; StkId top; /\* first free slot in the stack \*/ StkId base; /\* base of current function \*/ global_State *l_G; CallInfo \*ci; /\* call info for current function */ const Instruction \*savedpc; /\* \`savedpc’ of current function */ StkId stack_last; /\* last free slot in the stack \*/ StkId stack; /\* stack base \*/ CallInfo \*end_ci; /\* points after end of ci array*/ CallInfo \*base_ci; /\* array of CallInfo’s */ int stacksize; int size_ci; /\* size of array \`base_ci’ \*/ unsigned short nCcalls; /\* number of nested C calls \*/ unsigned short baseCcalls; /\* nested C calls when resuming coroutine \*/ lu_byte hookmask; lu_byte allowhook; int basehookcount; int hookcount; lua_Hook hook; TValue l_gt; /\* table of globals \*/ TValue env; /\* temporary place for environments \*/ GCObject \*openupval; /\* list of open upvalues in this stack */ GCObject *gclist; struct lua_longjmp \*errorJmp; /\* current error recover point */ ptrdiff_t errfunc; /\* current error handling function (stack index) \*/ };
每一个lua虚拟机可能会包含很多个lua_state结构。而所有的lua_State所共享的数据(比如string,比如gc数据等), 都将会放到global_State中。
这里要注意所有的GC数据中,string和其他的是不同的,他和其他的GC对象分开管理,我们先来看非string类的对象如何管理。先来看global_State这个结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 typedef struct global_State { stringtable strt; /\* hash table for strings \*/ lua_Alloc frealloc; /\* function to reallocate memory \*/ void \*ud; /\* auxiliary data to \`frealloc’ */ lu_byte currentwhite; lu_byte gcstate; /\* state of garbage collector \*/ int sweepstrgc; /\* position of sweep in \`strt’ \*/ GCObject \*rootgc; /\* list of all collectable objects */ GCObject *\*sweepgc; /\* position of sweep in \`rootgc’ */ GCObject \*gray; /\* list of gray objects */ GCObject \*grayagain; /\* list of objects to be traversed atomically */ GCObject \*weak; /\* list of weak tables (to be cleared) */ GCObject \*tmudata; /\* last element of list of userdata to be GC */ Mbuffer buff; /\* temporary buffer for string concatentation \*/ lu_mem GCthreshold; lu_mem totalbytes; /\* number of bytes currently allocated \*/ lu_mem estimate; /\* an estimate of number of bytes actually in use \*/ lu_mem gcdept; /\* how much GC is \`behind schedule’ \*/ int gcpause; /\* size of pause between successive GCs \*/ int gcstepmul; /\* GC \`granularity’ \*/ lua_CFunction panic; /\* to be called in unprotected errors \*/ TValue l_registry; struct lua_State *mainthread; UpVal uvhead; /\* head of double-linked list of all open upvalues \*/ struct Table \*mt[NUM_TAGS]; /\* metatables for basic types */ TString \*tmname[TM_N]; /\* array with tag-method names */ } global_State;
着重来看GC相关的几个数据结构。当前虚拟机的所有的GC对象都会保存在一个链表中,这个链表的根就是global_State的rootgc中。我们来看roottgc的初始化,代码在lua_newstate中:
1 2 g->rootgc = obj2gco(L);
然后每一个被创建的gc对象都会被挂载到这个链表中。挂载函数就是luaC_link。这个函数主要用来将需要gc的对象link到全局的global_state中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void luaC_link (lua_State \*L, GCObject \*o, lu_byte tt) { global_State *g = G(L); o->gch.next = g->rootgc; g->rootgc = o; o->gch.marked = luaC_white(g); o->gch.tt = tt; }
通过上面的函数,我们可以看到每次新的gc对象插入的时候,总是放到链表的最前端(rootgc 为当前对象).
不过这里的upvalue和userdata都是使用另外的方法挂载到全局的涟中的,先来看upvalue(upvalue是什么,我这里就不介绍了,前面的lua源码分析有介绍过的).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void luaC_linkupval (lua_State \*L, UpVal \*uv) { global_State *g = G(L); GCObject *o = obj2gco(uv); o->gch.next = g->rootgc; /\* link upvalue into \`rootgc’ list \*/ g->rootgc = o; if (isgray(o)) { if (g->gcstate == GCSpropagate) { gray2black(o); /\* closed upvalues need barrier \*/ luaC_barrier(L, uv, uv->v); } else { /\* sweep phase: sweep it (turning it into white) \*/ makewhite(g, o); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); } } }
可以看到和luaC_link不同的是,进行了gc算法的一些操作,这里我们先搁置,后续介绍gc算法的时候,会再来看这里。
然后就是userdata的特殊处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Udata \*luaS_newudata (lua_State \*L, size_t s, Table *e) { Udata *u; if (s > MAX_SIZET – sizeof(Udata)) luaM_toobig(L); u = cast(Udata *, luaM_malloc(L, s + sizeof(Udata))); u->uv.marked = luaC_white(G(L)); /\* is not finalized \*/ u->uv.tt = LUA_TUSERDATA; u->uv.len = s; u->uv.metatable = NULL; u->uv.env = e; /\* chain it on udata list (after main thread) \*/ u->uv.next = G(L)->mainthread->next; G(L)->mainthread->next = obj2gco(u); return u; }
可以看到它是和上面的两种方式完全不同,这是因为userdata一般来说都有自己的gc方法,因此最好能够放在一起处理,因此这里会将udata放到最末尾.
在lua中的gc算法,是mark-sweep算法,这个算法简单来说就分为两步,第一步是遍历所有的GCObject的对象,然后做标记 。 第二步是遍历所有可回收对象,然后清除没有做过标记的对象。 在lua中通过两个参数来控制gc的频率和周期,分别是garbage-collector pause 和garbage-collector step multiplier, 这两个值都是使用百分比(100表示 100%). 其中garbage-collector pause控制回收器等待多久开始一次新的垃圾回收,比如默认值是200,那么就说明只有当等待内存使用为上一次gc时的2倍才会进行下一次gc。而garbage-collector step multiplier控制垃圾回收的相对速度(相对于分配的速度), 默认也是200,说明垃圾回收的速度为内存分配的两倍,这两个值都可以通过lua_gc来修改(LUA_GCSETPAUSE与LUA_GCSETSTEPMUL).
而在lua 5.1中实现的是 Tri-color marking 算法,算法描述见wiki(http://en.wikipedia.org/wiki/Garbage_collection_%28computer_science%29#Tri-color_marking ) , 这个算法将每一个对象分为三种颜色,分别是白色(初始状态), 灰色(和root有连接,可是它所连接的对象还没有被扫描,因此这个状态不能被gc),以及黑色(可以被释放的对象集合), 所有的对象都会经历从白色到灰色再到黑色的过程.
算法的具体步骤如下:
Create initial white, grey, and black sets; these sets will be used to maintain progress during the cycle.
2.
Initially the white set or condemned set is the set of objects that are candidates for having their memory recycled.
The black set is the set of objects that can cheaply be proven to have no references to objects in the white set, but are also not chosen to be candidates for recycling; in many implementations, the black set starts off empty.
The grey set is all the objects that are reachable from root references but the objects referenced by grey objects haven’t been scanned yet. Grey objects are known to be reachable from the root, so cannot be garbage collected: grey objects will eventually end up in the black set. The grey state means we still need to check any objects that the object references.
The grey set is initialised to objects which are referenced directly at root level; typically all other objects are initially placed in the white set.
Objects can move from white to grey to black, never in the other direction.
Pick an object from the grey set. Blacken this object (move it to the black set), by greying all the white objects it references directly. This confirms that this object cannot be garbage collected, and also that any objects it references cannot be garbage collected.
Repeat the previous step until the grey set is empty.
When there are no more objects in the grey set, then all the objects remaining in the white set have been demonstrated not to be reachable, and the storage occupied by them can be reclaimed.
然后我们来看具体实现,我们就从最常见的table来分析。首先来看lua gc的启动。这里核心方法就是luaC_checkGC这个宏, 因为gc的启动一般来说就是通过这个宏开始的。
1 2 3 4 5 6 7 8 #define luaC_checkGC(L) { \ condhardstacktests(luaD_reallocstack(L, L->stacksize – EXTRA_STACK – 1)); \ if (G(L)->totalbytes >= G(L)->GCthreshold) \ luaC_step(L); }
这里我们可以看到它会比较当前分配的字节数与GCthreshold进行比较,如果大于这个值才会进行step。而GCthreshold就是一个阀值..
然后来看luaC_step这个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 void luaC_step (lua_State *L) { global_State *g = G(L); //首先计算需要回收的内存大小 l_mem lim = (GCSTEPSIZE/100) * g->gcstepmul; if (lim == 0) lim = (MAX_LUMEM-1)/2; /\* no limit \*/ g->gcdept += g->totalbytes – g->GCthreshold; do { //开始gc处理 lim -= singlestep(L); if (g->gcstate == GCSpause) break; } while (lim > 0); if (g->gcstate != GCSpause) { if (g->gcdept < GCSTEPSIZE) g->GCthreshold = g->totalbytes + GCSTEPSIZE; /\* – lim/g->gcstepmul;\*/ else { g->gcdept -= GCSTEPSIZE; g->GCthreshold = g->totalbytes; } } else { setthreshold(g); } }
这里主要就是singlestep函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 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执行顺序也是按照上面的状态的从大到小开始。
其中GCSpause是初始化状态。在这个状态主要就是标记主线程对象(也就是从白色染成灰色)。我们就从这个状态开始,我们可以看到这个状态的处理很简单,就是调用markroot函数来标记对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static void markroot (lua_State *L) { global_State *g = G(L); g->gray = NULL; g->grayagain = NULL; g->weak = NULL; markobject(g, g->mainthread); /\* make global table be traversed before main stack \*/ markvalue(g, gt(g->mainthread)); markvalue(g, registry(L)); markmt(g); g->gcstate = GCSpropagate; }
上面的markXXX的几个函数最终都会调用reallymarkobject函数,因此我们就从这个函数开始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 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); } }
这个函数我们可以看到首先它会将白色染成灰色,然后会根据对象的类型来做不同的操作,这里特殊操作就3种类型,分别是string(不通过gc管理),userdata, 以及upval,其他的类型都是将灰色的对象连接到global state的链表中.
当GCSpause状态之后,会进入GCSpropagate状态(上面的markroot函数最后一个语句).这个状态也是一个标记过程,并且这个状态会被进入多次,也就是分布迭代。如果gray对象一直存在的话,会反复调用propagatemark函数,等所有的gray对象都被标记了,那么就将会进入atomic函数处理。这个函数,顾名思义,也就是原子操作,最终在这个状态之后,gc进入清理字符串的阶段.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 static l_mem propagatemark (global_State *g) { GCObject *o = g->gray; lua_assert(isgray(o)); gray2black(o); switch (o->gch.tt) { case LUA_TTABLE: { Table *h = gco2h(o); g->gray = h->gclist; if (traversetable(g, h)) /\* table is weak? \*/ black2gray(o); /\* keep it gray \*/ return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(Node) * sizenode(h); } case LUA_TFUNCTION: { Closure *cl = gco2cl(o); g->gray = cl->c.gclist; traverseclosure(g, cl); return (cl->c.isC) ? sizeCclosure(cl->c.nupvalues) : sizeLclosure(cl->l.nupvalues); } case LUA_TTHREAD: { lua_State *th = gco2th(o); g->gray = th->gclist; th->gclist = g->grayagain; g->grayagain = o; black2gray(o); traversestack(g, th); return sizeof(lua_State) + sizeof(TValue) * th->stacksize + sizeof(CallInfo) * th->size_ci; } case LUA_TPROTO: { Proto *p = gco2p(o); g->gray = p->gclist; traverseproto(g, p); return sizeof(Proto) + sizeof(Instruction) * p->sizecode + sizeof(Proto \*) \* p->sizep + sizeof(TValue) * p->sizek + sizeof(int) * p->sizelineinfo + sizeof(LocVar) * p->sizelocvars + sizeof(TString \*) \* p->sizeupvalues; } default: lua_assert(0); return 0; } }
可以看到在propagate状态,也会根据对象类型来进行标记,这里我们可以看到它首先会把当前的对象节点标记为黑色,然后再进行后续处理,主要来看table类型。它会将对象挂载到gray链表,然后开始遍历标记table,这里注意如果table是weak的,那么则会将black节点重新染成gray的, 最后返回这次标记的内存大小,而核心方法就在traversetable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 static int traversetable (global_State \*g, Table \*h) { int i; int weakkey = 0; int weakvalue = 0; const TValue *mode; if (h->metatable) markobject(g, h->metatable); mode = gfasttm(g, h->metatable, TM_MODE); if (mode && ttisstring(mode)) { /\* is there a weak mode? \*/ weakkey = (strchr(svalue(mode), ‘k’) != NULL); weakvalue = (strchr(svalue(mode), ‘v’) != NULL); if (weakkey || weakvalue) { /\* is really weak? \*/ h->marked &= ~(KEYWEAK | VALUEWEAK); /\* clear bits \*/ h->marked |= cast_byte((weakkey << KEYWEAKBIT) | (weakvalue << VALUEWEAKBIT)); h->gclist = g->weak; /\* must be cleared after GC, … \*/ g->weak = obj2gco(h); /\* … so put in the appropriate list \*/ } } if (weakkey && weakvalue) return 1; if (!weakvalue) { i = h->sizearray; while (i–) markvalue(g, &h->array[i]); } //对表的数组部分进行处理. i = sizenode(h); while (i–) { Node *n = gnode(h, i); lua_assert(ttype(gkey(n)) != LUA_TDEADKEY || ttisnil(gval(n))); if (ttisnil(gval(n))) removeentry(n); /\* remove empty entries \*/ else { lua_assert(!ttisnil(gkey(n))); if (!weakkey) markvalue(g, gkey(n)); if (!weakvalue) markvalue(g, gval(n)); } } return weakkey || weakvalue; }
traversetable方法首先会标记元表,然后主要是对weak table进行特殊处理,由于weak table是弱引用,因此这里将会在gc之后单独处理弱表(g->weak).如果不是weak表,那么将会对这个对象进行mark。最后返回值是表示当前的表是否处于weak模式.
如果traversetable返回1,则表示表是weak模式,此时重新将对象的颜色染回灰色,因为weak table,后续会统一处理,也就是脱离lua的gc.
最后如果已经将所有的gray对象染色完毕(weak 表的话,gray对象会被移到g->weak),那么GCSpropagate状态最后将会进入atomic这个函数。这个函数之所以叫atomic,是因为在这个状态下lua的标记是不会被打断的,它最终会做一次清理,也就是对于在标记期间有改变的对象再次进行mark。这里就涉及到一个barrier的概念,之所以要有barrier,是因为由于lua的gc是分步的,因此在进入最终的清理状态之前,有可能被标记的对象的颜色已经改变(比如本来是白色,可是我们第一次扫描之后,它又被使用了,此时自然就变成灰色了,或者是已经被染色为黑色了,可是对象后续又没有对应的引用了),在这些情况下,都会将颜色染回灰色,要么是barrier fwd(white->gray),要么是 barrier back(black->gray).后续我们会详细介绍barrier,这里先跳过.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 static void atomic (lua_State *L) { global_State *g = G(L); size_t udsize; /\* total size of userdata to be finalized \*/ /\* remark occasional upvalues of (maybe) dead threads \*/ remarkupvals(g); /\* traverse objects cautch by write barrier and by ‘remarkupvals’ \*/ propagateall(g); /\* remark weak tables \*/ g->gray = g->weak; g->weak = NULL; lua_assert(!iswhite(obj2gco(g->mainthread))); markobject(g, L); /\* mark running thread \*/ markmt(g); /\* mark basic metatables (again) \*/ propagateall(g); /\* remark gray again \*/ g->gray = g->grayagain; g->grayagain = NULL; propagateall(g); udsize = luaC_separateudata(L, 0); /\* separate userdata to be finalized \*/ marktmu(g); /\* mark \`preserved’ userdata \*/ udsize += propagateall(g); /\* remark, to propagate \`preserveness’ \*/ cleartable(g->weak); /\* remove collected objects from weak tables \*/ /\* flip current white \*/ g->currentwhite = cast_byte(otherwhite(g)); //这里注意,这个值后面清理string的时候会用到. g->sweepstrgc = 0; //清理其他对象的时候,会用到.到达这里说明在rootgc上挂在的都是不可达对象,因此我们需要将他们后续清理. g->sweepgc = &g->rootgc; g->gcstate = GCSsweepstring; g->estimate = g->totalbytes – udsize; /\* first estimate \*/ }
这里还有一个要注意的,那就是处理useadata,由于userdata是会有自己的gc方法,因此userdata最终会单独处理(前面我们看到链接到gcroot的时候,也是放在最末尾).来看luaC_separateudata:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 size_t luaC_separateudata (lua_State *L, int all) { global_State *g = G(L); size_t deadmem = 0; //取出userdata GCObject **p = &g->mainthread->next; GCObject *curr; //开始遍历 while ((curr = *p) != NULL) { if (!(iswhite(curr) || all) || isfinalized(gco2u(curr))) p = &curr->gch.next; /\* don’t bother with them \*/ else if (fasttm(L, gco2u(curr)->metatable, TM_GC) == NULL) { markfinalized(gco2u(curr)); /\* don’t need finalization \*/ p = &curr->gch.next; } else { /\* must call its gc method \*/ //到达这里说明有gc方法 deadmem += sizeudata(gco2u(curr)); markfinalized(gco2u(curr)); *p = curr->gch.next; /\* link \`curr’ at the end of \`tmudata’ list \*/ if (g->tmudata == NULL) /\* list is empty? \*/ g->tmudata = curr->gch.next = curr; /\* creates a circular list \*/ else { curr->gch.next = g->tmudata->gch.next; g->tmudata->gch.next = curr; g->tmudata = curr; } } } return deadmem; }
通过上面我们可以看到这里并没有真正的释放userdata,只是将有gc方法的userdata链接到g->tmudata上。我们要谨记,在lua gc中,只有清理阶段才会真正释放内存。
然后我们来看GCSsweepstring状态,也就是清理string。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 case GCSsweepstring: { lu_mem old = g->totalbytes; //这里可以看到每次进来都会对当前的hash进行释放,这里sweepstrgc相当于一个索引 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; } #define sweepwholelist(L,p) sweeplist(L,p,MAX_LUMEM)
可以看到核心的方法就是 sweeplist,这个方法是清理阶段所有的对象都会调用这个方法.这里注意有一个other white,这个也就是当我们在标记之后,清理之前新添加的对象,我们都会认为他们是other white,等待下次处理.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 static GCObject *\*sweeplist (lua_State \*L, GCObject **p, lu_mem count) { GCObject *curr; global_State *g = G(L); int deadmask = otherwhite(g); //遍历gc对象 while ((curr = *p) != NULL && count– > 0) { if (curr->gch.tt == LUA_TTHREAD) /\* sweep open upvalues of each thread \*/ sweepwholelist(L, &gco2th(curr)->openupval); //如果还没有dead,则会重新染成白色,等待下次处理 if ((curr->gch.marked ^ WHITEBITS) & deadmask) { /\* not dead? \*/ lua_assert(!isdead(g, curr) || testbit(curr->gch.marked, FIXEDBIT)); makewhite(g, curr); /\* make it white (for next cycle) \*/ p = &curr->gch.next; } else { /\* must erase \`curr’ \*/ lua_assert(isdead(g, curr) || deadmask == bitmask(SFIXEDBIT)); *p = curr->gch.next; if (curr == g->rootgc) /\* is the first element of the list? \*/ g->rootgc = curr->gch.next; /\* adjust first \*/ //释放对象 freeobj(L, curr); } } return p; }
我们来看最后一个状态 GCSfinalize,这个状态最终会处理useadata,主要是调用GCTM 来调用userdata的gc方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 static void GCTM (lua_State *L) { global_State *g = G(L); GCObject \*o = g->tmudata->gch.next; /\* get first element */ Udata *udata = rawgco2u(o); const TValue *tm; /\* remove udata from \`tmudata’ \*/ if (o == g->tmudata) /\* last element? \*/ g->tmudata = NULL; else g->tmudata->gch.next = udata->uv.next; udata->uv.next = g->mainthread->next; /\* return it to \`root’ list \*/ g->mainthread->next = o; makewhite(g, o); //取的gc 元方法. tm = fasttm(L, udata->uv.metatable, TM_GC); if (tm != NULL) { lu_byte oldah = L->allowhook; lu_mem oldt = g->GCthreshold; L->allowhook = 0; /\* stop debug hooks during GC tag method \*/ g->GCthreshold = 2\*g->totalbytes; /\* avoid GC steps */ setobj2s(L, L->top, tm); setuvalue(L, L->top+1, udata); L->top += 2; //调用对应的gc方法. luaD_call(L, L->top – 2, 0); L->allowhook = oldah; /\* restore hooks \*/ g->GCthreshold = oldt; /\* restore threshold \*/ } }
最后我们来看 write barrier,它主要用来解决在扫描过程中,一些已经染色的对象,或者说新添加的对象能够被正确的染色。来看对应的4个api:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #define luaC_barrier(L,p,v) { if (valiswhite(v) && isblack(obj2gco(p))) \ luaC_barrierf(L,obj2gco(p),gcvalue(v)); } #define luaC_barriert(L,t,v) { if (valiswhite(v) && isblack(obj2gco(t))) \ luaC_barrierback(L,t); } #define luaC_objbarrier(L,p,o) \ { if (iswhite(obj2gco(o)) && isblack(obj2gco(p))) \ luaC_barrierf(L,obj2gco(p),obj2gco(o)); } #define luaC_objbarriert(L,t,o) \ { if (iswhite(obj2gco(o)) && isblack(obj2gco(t))) luaC_barrierback(L,t); }
其中带obj的表示是针对gc类型的,而不带obj的api表示是针对Tvalue类型的。而对应的luaC_barrierf(forwardl表示 从白色到灰色,而luaC_barrierback(back)表示从黑色到灰色. 并且可以看到table被拿出来特殊处理,这里之所以特殊处理,是因为table的修改是很频繁的,而其他的对象之间联系会比较少.因此前面的代码我们也可以看到(atomic),也是针对table对象特殊处理.
这四个函数都是用于将对象v(o)关联到p(t)时,需要做的操作.先来看luaC_barrierf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void luaC_barrierf (lua_State \*L, GCObject \*o, GCObject *v) { global_State *g = G(L); lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o)); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); lua_assert(ttype(&o->gch) != LUA_TTABLE); /\* must keep invariant? \*/ if (g->gcstate == GCSpropagate) reallymarkobject(g, v); /\* restore invariant \*/ else /\* don’t mind \*/ makewhite(g, o); /\* mark as white just to avoid other barriers \*/ }
这个函数很简单,我们可以看到只有处于mark状态时,我们才需要重新mark将要挂在的对象,否则就直接把对象染成白色(和需要清理的白色不同)。
然后是luaC_barrierback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void luaC_barrierback (lua_State \*L, Table \*t) { global_State *g = G(L); GCObject *o = obj2gco(t); lua_assert(isblack(o) && !isdead(g, o)); lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause); black2gray(o); /\* make table gray (again) \*/ t->gclist = g->grayagain; g->grayagain = o; }
可以看到是直接变为灰色,然后再把对象加载到grayagain这个链表上.而最终会在atomic函数中特殊处理.