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#實現?

github.com
圖標
C實現?

github.com


推薦閱讀:
相关文章