Lua 源码学习笔记(4)GC
参考书籍:《Lua设计与实现》
作者书籍对应Github:https://github.com/lichuang/Lua-Source-Internal
云风的 BLOG,Lua GC 的工作原理:https://blog.codingnow.com/2018/10/lua_gc.html
云风的 BLOG,Lua GC 的源码剖析 (1):https://blog.codingnow.com/2011/03/lua_gc_1.html
探索Lua5.2内部实现:Garbage Collection(1) 原理:https://blog.csdn.net/yuanlin2008/article/details/8558103
探索Lua5.2内部实现:Garbage Collection(2):https://blog.csdn.net/yuanlin2008/article/details/8684869
深入探究Lua的GC算法(上)-《Lua设计与实现》:https://www.cnblogs.com/zblade/p/8824376.html
- Lua版本:5.3.5
概述
-
GC一般原理:遍历系统所有对象,没有引用的对象就认为是可以回收的,可以删除。
-
引用计数算法:对象被引用的时候加1,反之减1,如果引用数量为0,就是没有引用。这个算法优点是不用遍历所有对象,但是有个缺点是,会有循环引用问题。
-
标记清除算法:首先扫描标记系统中所有对象,被扫描并且标记到的对象认为是可达的(reachable),这些对象不会不会被回收,反之可以。(Lua使用这种)
Lua5.0版本GC算法:双色标记清除法
- 缺点:不能被打断,标记阶段和回收阶段必须一起完成。(即如果中间过程加入新对象,标记为白色就会被回收,标记为黑色就还没有被扫描过。)
Lua5.1版本GC算法:三色标记清除法
- 白色:待访问状态。表示对象还没有被垃圾回收的标记过程访问到。
- 灰色:待扫描状态。表示对象已经被垃圾回收访问到了,但是对象本身对于其他对象的引用还没有进行遍历访问。
- 黑色:已扫描状态。表示对象已经被访问到了,并且也已经遍历了对象本身对其他对象的引用。
每个新创建的对象颜色设置为白色
//初始化阶段
遍历root节点中引用的对象,从白色置为灰色,并且放入到灰色节点列表中
//标记阶段
while(灰色链表中还有未扫描的元素):
从中取出一个对象,将其置为黑色
遍历这个对象关联的其他所有对象:
if 为白色:
标记为灰色,加入到灰色链表中(insert to the head)
//回收阶段
遍历所有对象:
if 为白色,
没有被引用的对象,执行回收
else
重新塞入到对象链表中,等待下一轮GC
- 双白色:简单地说, Lua 中的白色分为“当前白色”( currentwhite )和“非当前白色”( otherwhite ) 。这两种白色的状态交替使用。代码在回收时会做判断,如果某个对象的白色不是此次GC 使用的白色状态,那么将不会认为是没有被引用的对象而回收,这样的白色对象将留在下一次GC 中进行扫描。
// lgc.h
/* Layout for bit use in 'marked' field: */
#define WHITE0BIT 0 /* object is white (type 0) */
#define WHITE1BIT 1 /* object is white (type 1) */
#define BLACKBIT 2 /* object is black */
#define FINALIZEDBIT 3 /* object has been marked for finalization */
/* bit 7 is currently used by tests (luaL_checkmemory) */
具体流程
创建对象luaC_newobj
- 创建GCObject
- 标记白色
- 设置类型
- 放到GC列表
// lgc.c
/*
** create a new collectable object (with given type and size) and link
** it to 'allgc' list.
*/
GCObject *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; // 插在GC列表表头
g->allgc = o; /* list of all collectable objects */
return o;
}
执行单步singlestep
// lgc.h
/*
** Possible states of the Garbage Collector,GC过程中的步骤
*/
#define GCSpropagate 0
#define GCSatomic 1
#define GCSswpallgc 2
#define GCSswpfinobj 3
#define GCSswptobefnz 4
#define GCSswpend 5
#define GCScallfin 6
#define GCSpause 7
static lu_mem singlestep (lua_State *L) {
global_State *g = G(L);
switch (g->gcstate) {
case GCSpause: { // 一段GC循环的开始
g->GCmemtrav = g->strt.size * sizeof(GCObject*);
restartcollection(g); // 标记为灰色:mark root set and reset all gray lists, to start a new collection
g->gcstate = GCSpropagate;
return g->GCmemtrav;
}
case GCSpropagate: { // 传播阶段?
g->GCmemtrav = 0;
lua_assert(g->gray);
propagatemark(g); // 转换灰成黑(除了线程,一直是灰)
if (g->gray == NULL) // 如果没有灰对象了,就执行下一个阶段
g->gcstate = GCSatomic; /* finish propagate phase */
return g->GCmemtrav; /* memory traversed in this step */
}
case GCSatomic: {
lu_mem work;
propagateall(g); /* make sure gray list is empty */
work = atomic(L); /* work is what was traversed by 'atomic' */
entersweep(L); // 进入回收阶段
g->GCestimate = gettotalbytes(g); /* first estimate */;
return work;
}
case GCSswpallgc: { /* sweep "regular" objects */
return sweepstep(L, g, GCSswpfinobj, &g->finobj);
}
case GCSswpfinobj: { /* sweep objects with finalizers */
return sweepstep(L, g, GCSswptobefnz, &g->tobefnz);
}
case GCSswptobefnz: { /* sweep objects to be finalized */
return sweepstep(L, g, GCSswpend, NULL);
}
case GCSswpend: { /* finish sweeps */
makewhite(g, g->mainthread); /* sweep main thread */
checkSizes(L, g); // 有必要时,收缩String表
g->gcstate = GCScallfin;
return 0;
}
case GCScallfin: { /* call remaining finalizers */
if (g->tobefnz && g->gckind != KGC_EMERGENCY) {
int n = runafewfinalizers(L); // 针对每个对象调用回收方法
return (n * GCFINALIZECOST);
}
else { /* emergency mode or no more finalizers */
g->gcstate = GCSpause; /* finish collection */
return 0;
}
}
default: lua_assert(0); return 0;
}
}
/*
** mark root set and reset all gray lists, to start a new collection
** 标记 mainthread,l_registry,G表 为灰色,最终跑到reallymarkobject
*/
static void restartcollection (global_State *g) {
g->gray = g->grayagain = NULL;
g->weak = g->allweak = g->ephemeron = NULL;
markobject(g, g->mainthread);
markvalue(g, &g->l_registry);
markmt(g);
markbeingfnz(g); /* mark any finalizing object left from previous cycle */
}
barrier操作
- 向前走一步:如果一个新创建对象的颜色是白色,而它被一个黑色对象引用了,那么将这个对象的颜色从白色变成灰色。
- 当前GC没有在扫描阶段标记,就标记这个对象,否则标记白色,等下次扫描。
- 向后走一步:黑色的对象回退到灰色,也就是这个原先已经被标记为黑色的对象需要重新被扫描。
- 对于表:只要有对象新增到Table,就回退到灰色重新扫。
/*
** barrier that moves collector forward, that is, mark the white object
** being pointed by a black object. (If in sweep phase, clear the black
** object to white [sweep it] to avoid other barrier calls for this
** same object.)
*/
void luaC_barrier_ (lua_State *L, GCObject *o, GCObject *v) {
global_State *g = G(L);
lua_assert(isblack(o) && iswhite(v) && !isdead(g, v) && !isdead(g, o));
if (keepinvariant(g)) /* must keep invariant? */
reallymarkobject(g, v); /* restore invariant */
else { /* sweep phase */
lua_assert(issweepphase(g));
makewhite(g, o); /* mark main obj. as white to avoid other barriers */
}
}
/*
** barrier that moves collector backward, that is, mark the black object
** pointing to a white object as gray again.
*/
void luaC_barrierback_ (lua_State *L, Table *t) {
global_State *g = G(L);
lua_assert(isblack(t) && !isdead(g, t));
black2gray(t); /* make table gray (again) */
linkgclist(t, g->grayagain);
}
进度控制
- 两种方式回收:
- 自动回收:GCdebt 大于0。
- 用户调API
/*
** Does one step of collection when debt becomes positive. 'pre'/'pos'
** allows some adjustments to be done only when needed. macro
** 'condchangemem' is used only for heavy tests (forcing a full
** GC cycle on every opportunity)
*/
#define luaC_condGC(L,pre,pos) \
{ if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \
condchangemem(L,pre,pos); }
/* more often than not, 'pre'/'pos' are empty */
#define luaC_checkGC(L) luaC_condGC(L,(void)0,(void)0)