lua C++物件記憶體管理
1:tolua++如何管理物件的生命週期
一般情況下,當lua裡對c++物件的引用變數可以被垃圾回收時,tolua++只是簡單的釋放userdata佔用的4位元組指標地址記憶體。但是也可以通過繫結或者程式碼指定的方式,讓tolua++真正釋放物件所佔記憶體。
繫結的方式,是指在將c++型別建構函式使用tolua++匯出到lua裡時,tolua++會自動生成new_local方法。如果在lua程式碼裡,用這個方法新建物件時,tolua++會呼叫tolua_register_gc方法,指明回收物件時回收物件記憶體。
在c++程式碼裡,使用tolua_pushusertype_and_takeownership;在lua程式碼裡,使用tolua.takeownership,都可以達到同樣的目的。
對於這些指定由tolua++回收記憶體的物件,如果其型別的解構函式也通過tolua++匯出了,則在回收記憶體時,會通過delete運算子,呼叫物件的解構函式。否則只會使用free方法回收。
tolua_register_gc方法,做的事情,是以物件指標為鍵,以物件metatable為值,將鍵值對儲存在tolua_gc表裡。在物件型別的metatable表的__gc方法裡,tolua++會檢查tolua_gc表是否包含以這個地址為鍵的表項。包含的話才會進行上述的記憶體回收工作。
2:userdata的回收
Lua 提供了一個自動的記憶體管理。這就是說你不需要關心建立新物件的分配記憶體操作,也不需要在這些物件不再需要時的主動釋放記憶體。 Lua 通過執行一個垃圾收集器來自動管理記憶體,以此一遍又一遍的回收死掉的物件(這是指 Lua 中不再訪問的到的物件)佔用的記憶體。 Lua 中所有物件都被自動管理,包括: table, userdata、 函式、執行緒、和字串。
Lua 實現了一個增量標記清除的收集器。它用兩個數字來控制垃圾收集週期: garbage-collector pause 和 garbage-collector step multiplier 。
garbage-collector pause 控制了收集器在開始一個新的收集週期之前要等待多久。隨著數字的增大就導致收集器工作工作的不那麼主動。小於 1 的值意味著收集器在新的週期開始時不再等待。當值為 2 的時候意味著在總使用記憶體數量達到原來的兩倍時再開啟新的週期。
step multiplier 控制了收集器相對記憶體分配的速度。更大的數字將導致收集器工作的更主動的同時,也使每步收集的尺寸增加。小於 1 的值會使收集器工作的非常慢過在 C 中呼叫 lua_gc 或是在 Lua 中呼叫
使用 C API ,你可以給 userdata 設定一個垃圾收集的元方法。這個元方法也被稱為結束子。結束子允許你用額外的資源管理器和 Lua 的記憶體管理器協同工作(比如關閉檔案、網路連線、或是資料庫連線,也可以說釋放你自己的記憶體)。
一個 userdata 可被回收,若它的 Metatable 中有 __gc 這個域 ,垃圾收集器就不立即收回它。取而代之的是,Lua 把它們放到一個列表中。最收集結束後,Lua 針對列表中的每個 userdata 執行了下面這個函式的等價操作:
function gc_event (udata) local h = metatable(udata).__gc
if h then,可能導致收集器永遠都結束不了當前週期。預設值為 2 ,這意味著收集器將以記憶體分配器的兩倍速執行。
你可以通
h(udata) end end
在每個垃圾收集週期的結尾,每個在當前週期被收集起來的 userdata 的結束子會以它們構造時的逆序依次呼叫。也就是說,收集列表中,最後一個在程式中被建立的 userdata 的結束子會被第一個呼叫。
3:程式碼
TOLUA_API void tolua_cclass (lua_State* L, const char* lname, const char* name, const char* base, lua_CFunction col){
//...
push_collector(L, name, col);
//...
/* now we also need to store the collector table for the const
instances of the class */
push_collector(L, cname, col);
}
註冊類的時候,向metatable中寫入.collector域。 TOLUA_API void tolua_classevents (lua_State* L)
{
//..
lua_pushstring(L,"__gc");
lua_pushstring(L, "tolua_gc_event");
lua_rawget(L, LUA_REGISTRYINDEX);
lua_rawset(L,-3);
}
_R.tolua_gc_event = closure{ func:class_gc_event, upvalue:上述兩個表格 }, 這是掛在每個類對應的metatable上的__gc方法。
tolua_gc_event就是一個閉包,下面看看它是如何建立的:
tolua_open(L)
/* create gc_event closure */
lua_pushstring(L, "tolua_gc_event");
lua_pushstring(L, "tolua_gc");
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushstring(L, "tolua_super");
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushcclosure(L, class_gc_event, 2);
lua_rawset(L, LUA_REGISTRYINDEX); 裡面有tolua_gc和tolua_super兩個表作為upvalue,class_gc_event作為gc的執行函式。 所以在註冊類的時候__gc的元方法就是一個閉包tolua_gc_event。 在tolua管理物件的生命週期講過,如果一個userdata會被垃圾回收,那麼會呼叫__gc元方法。 */
TOLUA_API int class_gc_event (lua_State* L) 一個閉包函式,還有兩個upvalue。
{
void* u = *((void**)lua_touserdata(L,1));
int top;
lua_pushvalue(L, lua_upvalueindex(1));--->tolua_gc表
lua_pushlightuserdata(L,u);
lua_rawget(L,-2); /* stack: gc umt */ 獲取tolua_gc表中userdata的metatable,gc[userdata] = metatable
lua_getmetatable(L,1); /* stack: gc umt mt */
/*fprintf(stderr, "checking type\n");*/
top = lua_gettop(L);
if (tolua_fast_isa(L,top,top-1, lua_upvalueindex(2))) /* make sure we collect correct type */
{
/*fprintf(stderr, "Found type!\n");*/
/* get gc function */
lua_pushliteral(L,".collector"); 檢查.collector域是否是一個函式
lua_rawget(L,-2); /* stack: gc umt mt collector */
if (lua_isfunction(L,-1)) {
/*fprintf(stderr, "Found .collector!\n");*/
}
else {
lua_pop(L,1);
/*fprintf(stderr, "Using default cleanup\n");*/
lua_pushcfunction(L,tolua_default_collect);
}
lua_pushvalue(L,1); /* stack: gc umt mt collector u */
lua_call(L,1,0); 呼叫註冊類的時候寫入.collector域的函式。
lua_pushlightuserdata(L,u); /* stack: gc umt mt u */
lua_pushnil(L); /* stack: gc umt mt u nil */
lua_rawset(L,-5); /* stack: gc umt mt */ 將userdata從tolua_gc表中移除
}
lua_pop(L,3);
return 0;
}
tolua_gc表:tolua_gc表中, 以C++指標為鍵, 值為metatable, 通過class_gc_event進行自動釋放 TOLUA_API int tolua_register_gc (lua_State* L, int lo)
{
int success = 1;
void *value = *(void **)lua_touserdata(L,lo);
lua_pushstring(L,"tolua_gc");
lua_rawget(L,LUA_REGISTRYINDEX);
lua_pushlightuserdata(L,value);
lua_rawget(L,-2);
if (!lua_isnil(L,-1)) /* make sure that object is not already owned */
success = 0;
else
{
lua_pushlightuserdata(L,value);
lua_getmetatable(L,lo);
lua_rawset(L,-4);
}
lua_pop(L,2);
return success;
}