1. 程式人生 > >Lua原始碼剖析(五)

Lua原始碼剖析(五)

這次主要來分析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),以及黑色(可以被釋放的物件集合), 所有的物件都會經歷從白色到灰色再到黑色的過程.

演算法的具體步驟如下:

  1. 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.
  1. 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.
  1. Repeat the previous step until the grey set is empty.
  1. 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; /\* &#8211; 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&#8217; 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 &#8211; 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 &#8211; 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), &#8216;k&#8217;) != NULL);

weakvalue = (strchr(svalue(mode), &#8216;v&#8217;) != 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, &#8230; \*/

g->weak = obj2gco(h); /\* &#8230; so put in the appropriate list \*/

}

}

if (weakkey && weakvalue) return 1;

if (!weakvalue) {

i = h->sizearray;

while (i&#8211;)

markvalue(g, &h->array[i]);

}

//對錶的陣列部分進行處理.

i = sizenode(h);

while (i&#8211;) {

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 &#8216;remarkupvals&#8217; \*/

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&#8217; userdata \*/

udsize += propagateall(g); /\* remark, to propagate \`preserveness&#8217; \*/

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 &#8211; 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&#8217;t bother with them \*/

else if (fasttm(L, gco2u(curr)->metatable, TM_GC) == NULL) {

markfinalized(gco2u(curr)); /\* don&#8217;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&#8217; at the end of \`tmudata&#8217; 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

相關推薦

Lua原始碼剖析()

這次主要來分析lua的gc。 首先lua中的資料型別包括下面9種,ni, Boolean, number, string, table,user data, thread , functions 以及 lightusedata.其中 string, table,thread , function 是會被垃圾回

Lua原始碼剖析(四)

前面三篇請看我前面的 blog 這篇主要來分析lua的虛擬機器的實現,我看的程式碼依舊是5.1 因此首先從luaL_loadfile開始,這個函式我們知道是在當前的lua state載入一個lua檔案,其中第二個引數就是filename。 其中LoadF結構很簡單,它用來表示一個load file: stru

STL原始碼剖析)【hash_set、hash_map】

hash_set 與set區別及聯絡 與set大同小異,set以RB-tree作為其底層,而hash_set以hash table作為其底層 兩者都是藉由其底層操作完成我們所看到的那些操作 二者最大的不同在於set的底層RB-tree有自動排序功能,所以反映在se

STL原始碼剖析)hashtable

文章目錄 1. hashtable概述 1.1 線性探測 1.2 二次探測 1.3 開鏈法 2. hashtable的桶與節點 3. hashtable迭代器 4. hashtable資料結構 5. has

STL原始碼剖析)關聯式容器--【set、multiset、map、multimap】

文章目錄 1. 寫在前面 2. set 2.1 set性質 2.2 set實現 2.3 multiset 3. map 3.1 map性質 3.2 pair 3.3 map實現

Spark2.3(三十)Spark Structured Streaming原始碼剖析(從CSDN中看到別人分析的原始碼的文章值得收藏)

從CSDN中讀取到關於spark structured streaming原始碼分析不錯的幾篇文章 spark原始碼分析--事件匯流排LiveListenerBus spark事件匯流排的核心是LiveListenerBus,其內部維護了多個AsyncEventQueue佇列用於儲存和分發

雲風的 BLOG: Lua GC 的原始碼剖析 (1)

/* ** Union of all Lua values */ typedef union { GCObject *gc; void *p; lua_Number n; int b; } Value; /* ** Tagge

雲風的 BLOG: Lua GC 的原始碼剖析 (2)

lua 的 GC 分為五個大的階段。GC 處於哪個階段(程式碼中被稱為狀態),依據的是 global_State 中的 gcstate 域。狀態以巨集形式定義在 lgc.h 的 14 行。 /* ** Possible states of th

雲風的 BLOG: Lua GC 的原始碼剖析 (3)

/* ** Garbage-collection function */ LUA_API int lua_gc (lua_State *L, int what, int data) { int res = 0; global_State

Redis原始碼剖析和註釋(二十)--- Redis Cluster 的通訊流程深入剖析(載入配置檔案、節點握手、分配槽)

Redis Cluster 通訊流程深入剖析 1. Redis Cluster 介紹和搭建 這篇部落格會介紹Redis Cluster的資料分割槽理論和一個三主三從叢集的搭建。 2. Redis Cluster 和 Redis Sentin

Redis原始碼剖析)訂閱與釋出

Redis提供了訂閱和釋出的功能,允許客戶端訂閱一個或多個頻道,當其他客戶端向某個頻道傳送訊息時,伺服器會將訊息轉發給所有訂閱該頻道的客戶端 這一點有點像群聊的功能,一個客戶端將訊息發往群中(向某個頻道傳送訊息),所有在群中的客戶端(訂閱該頻道的客戶端)都會收

openresty原始碼剖析——lua程式碼的載入

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 204 ngx_int_t 205

2020了你還不會Java8新特性?()收集器比較器用法詳解及原始碼剖析

收集器用法詳解與多級分組和分割槽 為什麼在collectors類中定義一個靜態內部類? static class CollectorImpl<T, A, R> implements Collector<T, A, R> 設計上,本身就是一個輔助類,是一個工廠。作用是給開發者提供常見的

設計模式實例(Lua)筆記之(Bridge模式)

制造 ria 先生 關聯 賣出 都在 int 每一個 賺大錢 1.描寫敘述 今天我要說說我自己,夢想中的我自己,我身價過億,有兩個大公司,一個是房地產公司,一個是服裝制造業,這兩個公司都非常賺錢,天天幫我在累加財富,事實上是什麽公司我倒是不關心,我關心的是是不

python重試庫retryiny原始碼剖析

  上篇博文介紹了常見需要進行請求重試的場景,本篇博文試著剖析有名的python第三方庫retrying原始碼。    在剖析其原始碼之前,有必要講一下retrying的用法,方便理解。    安裝:   pip install retryin

Caffe框架原始碼剖析(6)—池化層PoolingLayer

    卷積層ConvolutionLayer正向傳導的目標層往往是池化層PoolingLayer。池化層通過降取樣來降低卷積層輸出的特徵向量,同時改善結果,不易出現過擬合。最常用的降取樣方法有均值取樣(取區域平均值作為降取樣值)、最大值取樣(取區域最大值作為降取樣值)和隨機

Caffe框架原始碼剖析(5)—卷積層ConvolutionLayer

ConvolutionLayer是BaseConvolutionLayer的子類,功能較為簡單。類中不包含成員變數,僅包含幾個虛擬函式的實現。 conv_layer.hpp標頭檔案的定義如下: template <typename Dtype> class Convoluti

Caffe框架原始碼剖析(4)—卷積層基類BaseConvolutionLayer

    資料層DataLayer正向傳導的目標層是卷積層ConvolutionLayer。卷積層的是用一系列的權重濾波核與輸入影象進行卷積,具體實現是通過將影象展開成向量,作用矩陣乘法實現卷積。     同樣,首先看一下卷積層的類圖。

Caffe框架原始碼剖析(3)—資料層DataLayer

Caffe網路正向傳導時,首先進行的是DataLayer資料層的傳導。該層從檔案讀取資料,載入至它的上一層卷積層。反向傳播時,因為資料層不需要反傳,所以它的Backward_cpu()和Backward_gpu()都是空函式。下面看一下DataLayer類圖關係。 首先從父類Ba

Caffe框架原始碼剖析(2)—訓練網路

中間因為工程開發等雜七雜八原因暫停了Caffe原始碼分析,現在繼續補上。 上篇分析在函式 train() 中建立了網路,接下來就是進入訓練網路步驟了。 在函式train()中,使用前一步建立好的solver智慧指標物件呼叫函式Solve(), int train() {