lua垃圾回收

lgc.h文件中, 定义了以下几个宏, 是lua gc过程中的一些状态

1
2
3
4
5
6
7
8
#define GCSpropagate    0 // 标记阶段
#define GCSatomic 1 // 标记阶段 原子操作
#define GCSswpallgc 2 // 清理allgc
#define GCSswpfinobj 3 // 清理finobj
#define GCSswptobefnz 4 // 清理tobefnz
#define GCSswpend 5 // 清扫收尾
#define GCScallfin 6 // 调用__gc
#define GCSpause 7 // 暂停状态

global_State中, 定义了一些与GC相关的变量

  • GCdebt 表示触发GC的债务, 每当新建对象分配内存时就会累加到g->GCdebt, 达到临界值时触发GC
  • allgc 所有未被标记为终结的对象
  • finobj 所有被标记为终结的对象
  • tobefnz 所有准备终结的对象(准备调用__gc的对象)
  • fixedgc 不会被回收的对象
  • gray 灰色对象列表
  • grayagain 需要进行原子操作的对象列表
  • gcstate 当前状态

Lua内存回收使用的是三色法
白色:对象没有被垃圾回收器访问时候标记为白色
灰色:这个对象正处在被扫描,但是它所有引用都没访问完的时候被标记为灰色
黑色:当一个对象的所有引用都被扫描了以后的时候,这个对象被标记为黑色
在清除阶段, 会将所有白色对象回收

luaD_precall luaD_poscall lua_newuserdata lua_concat lua_createtable 等函数中, 会调用luaC_checkGC, 在这个函数中, 会去检查GCdebt, 达到内存回收条件, 就会调用luaC_step进行内存回收工作

  1. gcstate 初始为GCSpause状态, 会调用函数restartcollection(global_State)mainthread l_registry
    以及相关元表标记为灰色

    1
    2
    3
    4
    5
    6
    7
    8
    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 */
    }
  2. 进入标记阶段, 标记阶段分为GCSpropagateGCSatomic

    GCSpropagate: 从gray中不停的取出对象, 将其标记为黑色, 这一操作是分步进行的, 而且, 对象会根据对象类型的不同进行不同的处理

    1
    2
    3
    4
    5
    6
    7
    8
    gray2black(o);
    switch (o->tt) {
    case LUA_TTABLE:如果是弱表, 就回退到**灰色**, 否则遍历标记数组与哈希部分
    case LUA_TLCL: 对**upval**进行标记
    case LUA_TCCL: 对**upval**进行标记
    case LUA_TTHREAD:移动到**grayagain**, 进行原子操作处理
    case LUA_TPROTO:对**字符串** **upvalue** **局部变量**进行遍历标记
    }

    GCSatomic 除了处理 gray grayagain 等, 还会扫描finobj将可回收对象转移到tobefnz, 以及弱表

    1
    2
    3
    propagateall(g);  /* make sure gray list is empty */
    work = atomic(L); /* work is what was traversed by 'atomic' */
    entersweep(L);
  3. GCSswpallgc 清理allgc链表内的对象
    sweepstep(L, g, GCSswpfinobj, &g->finobj);

  4. GCSswpfinobj 清理finobj链表内的对象
    sweepstep(L, g, GCSswptobefnz, &g->tobefnz);

  5. GCSswptobefnz 清理tobefnz链表内的对象
    sweepstep(L, g, GCSswpend, NULL);

  6. GCSswpend 收尾工作, 标记mainthread上白色, 检查内存释放mainthread上的一些空间, 如:字符串表,连接缓冲区等

    1
    2
    makewhite(g, g->mainthread);  /* sweep main thread */
    checkSizes(L, g);
  7. GCScallfin 取出tobefnz链表中的对象, 调用其__gc, 然后放入到allgc

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static int runafewfinalizers (lua_State *L) {
    global_State *g = G(L);
    unsigned int i;
    lua_assert(!g->tobefnz || g->gcfinnum > 0);
    for (i = 0; g->tobefnz && i < g->gcfinnum; i++)
    GCTM(L, 1); /* call one finalizer */
    g->gcfinnum = (!g->tobefnz) ? 0 /* nothing more to finalize? */
    : g->gcfinnum * 2; /* else call a few more next time */
    return i;
    }