首先第一點,lua中的內存洩露和我們所說的c/c++中的內存洩露本質上是不一樣的。
lua中有垃圾回收機制(GC),所以理論上是不會有內存洩露的。當它進行GC的時候,會從根部開始掃描所有的對象,如果某個地方對這個對象還有引用,就不會把這個對象內存collect,這個對象就沒有被GC。所以lua中的內存洩露是指那些:已經沒有被使用了,但外部依然還有引用存在的對象。
--函數中應該被申明為local的對象忘記加local local function test() testTable = {} --這個testTabel會被存放在全局表_G中,GC時由於此對象還有引用存在,所以這裡總是會有一個table洩露。 local mt = {} --mt加了local修飾,函數調用完後,引用也不復存在了,GC時會被回收。 setmetatable(testTable, mt) end
lua中支持垃圾回收機制的對象有五種:string,table,function,full userdata,thread。而他們的引用直接或間接的保存到:lua_state對象,_G全局表,Registry注冊表,global_state->mt中。
在腳本中:
運行的lua腳本本身就是lua_state。_G就是_G全局表。Registry表可以用debug.getregistry獲取。global_mt可以用debug.getmetatable獲取。所以我們就可以在腳本層次實現內存洩露的檢測模塊。
在搜索時需要注意的幾點:
table 額外搜索metatable,若metatable中的__mode取值為”k"、"v"或者”kv"需特殊處理(補充中有說明)function 額外搜索 enviroment,也是一個table。額外搜索upvalues,這個可以是任何類型。由於userdata在script層次不能被修改,所以搜搜他的metatable吧thread對象就是coroutine對象,在script中一般都不會創建多個coroutine,所以在腳本中沒搜索它。若是需求的話,獲取到它的線程函數,然後再按照第2步操作就可以了。
在檢測洩露之前,先搜索一下所有的對象,保存好起始的內存狀態,在程序執行之後執行幾次GC操作,然後再進行一次搜索,對比兩次的結果,多出來的那些就有可能是內存洩露了。 <喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+srmz5KO6PC9wPgo8cD5sdWHW0NPQ0rvW1r3Qd2Vha7HttcS2q7aro6zL/LXEbWV0YXRhYmxl1tC1xF9fbW9kZbG7yejWw86qobBr","v"或者”kv",表示保存在它中的鍵或值或鍵值都是一種弱引用狀態。
若一個對象的所有引用都是弱引用了,那麼這個對象也會被GC回收掉,所以對應的weak表中此對象的入口就沒有了。
所以我們可以用另外一種實現:就是把用戶自己創建的資源對象統統都丟到weak表中,運行完程序後強制GC,然後去查看weak表,若表中還保存著那個對象,就意味著這個對象還有外部引用(相對弱引用我們就叫它為強引用吧),資源沒有被GC掉,所以我們可以說這個對象很有可能是內存洩露了。
Lua的GC算法使用的所謂“Mark And Sweep”算法。簡單的理解,這個算法將GC分為兩個階段,一個是標記(mark)階段,這一階段將所有系統中引用的對象都逐一標記;而在清理(sweep)階段,將把在mark階段中沒有被標記的數據刪除。
在Lua中,使用幾種顏色來區分不同的結點:
collectgarbage函數提供了多項功能:停止垃圾回收,重啟垃圾回收,強制執行一次回收循環,強制執行一步垃圾回收,獲取Lua占用的內存,以及兩個影響垃圾回收頻率和步幅的參數。collectgarbage(opt,[,arg])
"stop"
停止垃圾收集器,如果它的運行。
"restart"
如果垃圾收集器已經停止,將重新啟動它。
"collect"
執行一次全垃圾收集循環。默認執行此操作
"count"
返回當前Lua中使用的內存量(以KB為單位)
"step"
單步執行一個垃圾收集. 步長 "Size" 由參數arg指定 (大型的值需要多步才能完成),如果要准確指定步長,需要多次實驗以達最優效果。如果步長完成一次收集循環,將返回True
"setpause"
設置 arg/100 的值作為暫定收集的時長;並返回設置前的值。默認為200
控制了收集器在開始一個新的收集周期之前要等待多久。 隨著數字的增大就導致收集器工作工作的不那麼主動。 小於 1 的值意味著收集器在新的周期開始時不再等待。 當值為 2 的時候意味著在總使用內存數量達到原來的兩倍時再開啟新的周期。
"setstepmul"
設置 arg/100 的值,作為步長的增幅(即新步長=舊步長*arg/100);並返回設置前的值。默認為200
控制了收集器的工作速度,這個速度是一個相對於內存分配的速度。更大的數字將導致收集器工作的更主動的同時,也使每步收集的尺寸增加。 小於 1 的值會使收集器工作的非常慢,可能導致收集器永遠都結束不了當前周期。 缺省值為200%,這意味著收集器將以內存分配器的兩倍速運行。
function test1() collectgarbage("collect")--為了有干淨的環境,先把可以收集的垃圾收集了 collectgarbage()--為了保證內存的收集的相對干淨,及內存的穩定,要執行多次收集 print("now,Lua內存為:",collectgarbage("count")) -->205.7158203125 KB local colen = {} --現在是局部變量 for i=1,5000 do table.insert(colen,{}) end print("now,Lua內存為:",collectgarbage("count"))-->860.4111328125 KB --創建5000個table,內存增加了655 KB end function collect1() print("now,Lua內存為:",collectgarbage("count"))-->608.060546875 KB collectgarbage() collectgarbage() print("now,Lua內存為:",collectgarbage("count"))-->204.8408203125 KB --最後與一開始只差只有1KB end
function test2() collectgarbage("collect")--為了有干淨的環境,先把可以收集的垃圾收集了 collectgarbage()--為了保證內存的收集的相對干淨,及內存的穩定,要執行多次收集 print("now,Lua內存為:",collectgarbage("count")) -->205.7158203125 KB colen = {} --現在是全部變量 for i=1,5000 do table.insert(colen,{}) end print("now,Lua內存為:",collectgarbage("count"))-->619.826171875 KB --創建5000個table,內存增加了414 KB;這些增加的內存,由於已放到了全局函數中,是永遠沒有機會被回收到了! end function collect2() print("now,Lua內存為:",collectgarbage("count"))-->596.7822265625 KB collectgarbage() collectgarbage() collectgarbage() print("now,Lua內存為:",collectgarbage("count"))-->489.189453125 KB --最後內存增加了284KB(489-205) end
垃圾回收器有兩個參數用於控制它的節奏:
第一個參數,稱為暫停時間,控制回收器在完成一次回收之後和開始下次回收之前要等待多久;
第二個參數,稱為步進系數,控制回收器每個步進回收多少內容。粗略地來說,暫停時間越小、步進系數越大,垃圾回收越快。這些參數對於程序的總體性能的影響難以預測,更快的垃圾回收器顯然會浪費更多的CPU周期,但是它會降低程序的內存消耗總量,並可能因此減少分頁。只有謹慎地測試才能給你最佳的參數值。