垃圾回收(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 交互点。注意以下几点:
- resurrection 循环:如果
__gc中将 userdata 重新放入全局表,它会被复活,但下次 GC 不会再调用__gc - 顺序不确定:多个 userdata 的
__gc调用顺序没有保证 - 不要在
__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;
}