lua內存泄漏檢測工具原理及設計
lua內存泄漏檢測工具原理及設計
9 人贊了文章
Google一下「lua內存泄漏檢測」,基本都是直接或間接指向雲風多年前寫的《一個 Lua 內存泄露檢查工具》,其思路是給虛擬機做個快照,記錄下所有gc對象地址及引用關係,然後通過對比兩次快照來分析內存泄漏情況。文章似乎把內存泄漏等同於某個gc對象的新增了。
然而,新增gc對象就代表內存泄漏?看下這段代碼:
local no_leak = {}function innocent() no_leak.a = {x = 1} no_leak.b = {y = 1}end
innocent函數每次執行都會新增兩個table並持有它們,但這明顯不是內存泄漏,而且這是很常見的寫法。
不新增gc對象就代表沒內存泄漏?也不是:
local local_leak = {}function make_leak() table.insert(local_leak, 1)end
這種泄漏文章提供的工具貌似就無能為力。它只記錄gc對象及gc對象間的引用關係。但數字不是gc對象。
帶GC語言的內存泄漏
C/C++這類語言的內存泄漏,是分配了內存忘了釋放,但GC會幫我們自動釋放這類內存。而在帶GC的語言的內存泄漏,則是往一個容器裡頭塞東西忘了刪掉。
往一個容器裡頭塞東西忘了刪掉會導致什麼現象?
當然是導致這容器變大,所以疑似內存泄漏檢測就變成了容器大小(是否遞增)檢測。
這在lua裡頭又特別簡單,因為。。lua只有一種容器--table。
lua內存泄漏檢查
核心代碼十分簡單,只有十來行C代碼:
typedef void (*TableSizeReport) (const void *p, int size);LUA_API void xlua_report_table_size(lua_State *L, TableSizeReport cb, int fast){ GCObject *p = G(L)->allgc; while (p != NULL) { if (p->tt == LUA_TTABLE) { Table *h = gco2t(p); cb(h, table_size(h, fast)); } p = p->next; }}
遍歷所有對象,如果是table,則把指針和size報告給調用者。
這個C代碼將由C#調用,並記錄下table的size信息,也灰常簡單:
static Data getSizeReport(LuaEnv env){ Data data = new Data(); data.Memroy = env.Memroy; LuaDLL.Lua.xlua_report_table_size(env.L, (IntPtr p, int size) => { data.TableSizes.Add(p, size); }, 0); return data;}
好了,接下來對比前後size信息,就可以找出可能存在內存泄漏的table的指針了,這裡就不貼代碼了,文章中所有代碼都可以在xLua開源項目中找到。
table詳細信息
光拿table的指針是沒啥用的,我們要得到更多信息才定位。
一般而言,table順其引用鏈往上找,都能歸結到三個地方:
1、upvalue,比如你在lua腳本定義的local xx = {};
2、全局變數;
3、c側共用的一個特殊table:registry;
當然,棧也可能引用table,但我們是在C#調用C代碼,當時沒跑lua,棧應該是空的,而且僅僅棧指向的對象,我們可以先不管,這對象要麼是臨時的,要麼後面還是被上面三個地方引用。
table詳細信息思路
1、獲取對象引用關係,生成反向索引表;
2、從反向索引表查找到疑似泄漏table,然後根據反向索引往上找,一直找到上述的三個根,生成路徑
一個典型泄漏信息報告是這樣的:
total memroy: 87potential leak(180) in {leak2.lua:local anthor_leak.a[1].b,_R.ref_anthor_leak.a[1].b}potential leak(181) in {_G.global_leak}potential leak(180) in {leak1.lua:local local_leak}
第一個行表示有個大小為180的疑似泄漏table,它被兩個地方引用了
一個是leak2.lua文件的局部變數anthor_leak,位於這個局部變數的a[1].b子節點
一個是registry表(上面的第三個地方),ref_anthor_leak.a[1].b子節點
快泄漏和慢泄漏
如果程序中存在一個泄漏很快以及一個泄漏很慢的地方,我們兩次對比table size信息,很可慢的因為沒漲而被無視。
這也沒關係,當你找到泄漏快的地方,解決了快的,慢的就能被檢查出來了。
測試例子也展示這這種情況。
就說這麼多,更詳細的情況看代碼就好了:
示例及C#實現C實現推薦閱讀: