Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

垃圾回收(GC)

Lua 5.4 支持两种 GC 模式:增量模式(incremental)分代模式(generational)。C 代码可以通过 API 与 GC 交互。

控制 GC

通用命令

lua_gc(L, LUA_GCCOLLECT, 0);       // 执行完整 GC 周期
lua_gc(L, LUA_GCSTOP, 0);          // 停止 GC
lua_gc(L, LUA_GCRESTART, 0);       // 重启 GC

增量模式(LUA_GCINC)

lua_gc(L, LUA_GCINC, pause, stepmul, stepsize);
  • pause:GC 等待内存增长的百分比
  • stepmul:步进倍率
  • stepsize:单步步长(KB)

分代模式(LUA_GCGEN)

lua_gc(L, LUA_GCGEN, minormul, majormul);
  • minormul: minor GC 的倍率
  • majormul: major GC 的倍率

Lua 5.4 默认使用增量模式,但可以通过 LUA_GCGEN 切换到分代模式。

查询内存使用

int kbytes = lua_gc(L, LUA_GCCOUNT, 0);        // 已用内存(KB) 
int bytes  = lua_gc(L, LUA_GCCOUNTB, 0);       // 已用内存的余数(字节) 

总字节数 ≈ kbytes * 1024 + bytes

弱引用与 GC

通过 metatable 的 __mode 字段可以创建弱引用 table:

lua_newtable(L);                    // 创建 table 
lua_newtable(L);                    // 创建其 metatable 
lua_pushstring(L, "kv");            // key 和 value 都是弱引用 
lua_setfield(L, -2, "__mode");
lua_setmetatable(L, -2);            // 设置 metatable 

当 key 或 value 不再被其他地方引用时,它们会从弱引用 table 中自动移除。

__gc 与终结器

Full userdata 的 __gc 是 Lua GC 最重要的 C 交互点。注意以下几点:

  1. resurrection 循环:如果 __gc 中将 userdata 重新放入全局表,它会被复活,但下次 GC 不会再调用 __gc
  2. 顺序不确定:多个 userdata 的 __gc 调用顺序没有保证
  3. 不要在 __gc 中做复杂操作:避免在 __gc 中抛出错误或创建新对象

C 层面的对象生命周期管理

如果你有一个 C 库管理的外部资源池,可以将 Lua userdata 作为句柄:

typedef struct {
    int handle;
} Resource;

static int resource_gc(lua_State *L) {
    Resource *r = (Resource *)luaL_checkudata(L, 1, "ResourceMT");
    release_handle(r->handle);
    return 0;
}

这样 Lua 代码可以像普通对象一样使用资源,而 C 库保证资源最终被释放。

完整代码


#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stdio.h>

#define BOX_MT "BoxMeta"

typedef struct {
    int id;
} Box;

static int box_gc(lua_State *L) {
    Box *b = (Box *)luaL_checkudata(L, 1, BOX_MT);
    printf("[gc] Box #%d collected\n", b->id);
    return 0;
}

static int box_new(lua_State *L) {
    int id = (int)luaL_checkinteger(L, 1);
    Box *b = (Box *)lua_newuserdatauv(L, sizeof(Box), 0);
    b->id = id;
    luaL_getmetatable(L, BOX_MT);
    lua_setmetatable(L, -2);
    return 1;
}

int luaopen_box(lua_State *L) {
    luaL_newmetatable(L, BOX_MT);
    lua_pushcfunction(L, box_gc);
    lua_setfield(L, -2, "__gc");
    lua_pop(L, 1);

    lua_newtable(L);
    lua_pushcfunction(L, box_new);
    lua_setfield(L, -2, "new");
    return 1;
}

int main(void) {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    luaopen_box(L);
    lua_setglobal(L, "box");

    int kbytes = lua_gc(L, LUA_GCCOUNT, 0);
    assert(kbytes > 0);

    const char *script = "local t = {}\n"
                         "for i = 1, 5 do\n"
                         "    t[i] = box.new(i)\n"
                         "end\n"
                         "print('created 5 boxes')\n"
                         "for i = 1, 5 do\n"
                         "    t[i] = nil\n"
                         "end\n"
                         "collectgarbage('collect')\n"
                         "print('after gc')\n";

    luaL_dostring(L, script);

    kbytes = lua_gc(L, LUA_GCCOUNT, 0);
    assert(kbytes > 0);

    lua_close(L);
    puts("10-gc: ok");
    return 0;
}