與python等腳本語言相似地,Lua也採用了自動內存管理(Garbage Collection),一個程序只需建立對象,而無需刪除對象。經過使用垃圾收集機制,Lua會自動刪除過時對象。垃圾回收機制能夠將程序員從C語言中常出現的內存泄漏、引用無效指針等底層bug中解放出來。html
咱們知道Python的垃圾回收機制使用了引用計數算法,當指向一個對象的全部名字都失效(超出生存期或程序員顯式del了)了,會將該對象佔用的內存回收。但對於循環引用是一個特例,垃圾收集器一般沒法識別,這樣會致使存在循環引用的對象上的引用計數器永遠不會變爲零,也就沒有機會被回收。python
一個在python中使用循環引用的例子:程序員
class main1: def __init__(self): print('The main1 constructor is calling...') def __del__(self): print('The main1 destructor is calling....') class main2: def __init__(self, m3, m1): self.m1 = m1 self.m3 = m3 print('The main2 constructor is calling...') def __del__(self): print('The main2 destructor is calling....') class main3: def __init__(self): self.m1 = main1() self.m2 = main2(self, self.m1) print('The main3 constructor is calling...') def __del__(self): print('The main3 destructor is calling....') # test main3()
輸出內容爲:算法
The main1 constructor is calling... The main2 constructor is calling... The main3 constructor is calling...
能夠看出,析構函數(__del__函數)沒有被調用,循環引用致使了內存泄漏。數組
垃圾收集器只能回收那些它認爲是垃圾的東西,不會回收那些用戶認爲是垃圾的東西。好比那些存儲在全局變量中的對象,即便程序不會再用到它們,但對於Lua來講它們也不是垃圾,除非用戶將這些對象賦值爲nil,這樣它們才能被釋放。但有時候,簡單地清除引用還不夠,好比將一個對象放在一個數組中時,它就沒法被回收,這是由於即便當前沒有其餘地方在使用它,但數組仍引用着它,除非用戶告訴Lua這項引用不該該阻礙此對象的回收,不然Lua是無從得知的。服務器
table中有key和value,這二者均可以包含任意類型的對象。一般,垃圾收集器不會回收一個可訪問table中做爲key或value的對象。也就是說,這些key和value都是強引用,它們會阻止對其所引用對象的回收。在一個弱引用table中,key和value是能夠回收的。函數
弱引用table(weak table)是用戶用來告訴Lua一個引用不該該阻礙對該對象的回收。所謂弱引用,就是一種會被垃圾收集器忽視的對象引用。若是一個對象的引用都是弱引用,該對象也會被回收,而且還能夠以某種形式來刪除這些弱引用自己。lua
弱引用table有3種類型:spa
一、具備弱引用key的table;
二、具備弱引用value的table;
三、同時具備弱引用key和value的table;指針
table的弱引用類型是經過其元表中的__mode字段來決定的。這個字段的值應爲一個字符串:
若是包含'k',那麼這個table的key是弱引用的;
若是包含'v',那麼這個table的value是弱引用的;
弱引用table的一個例子,這裏使用了collectgarbage函數強制進行一次垃圾收集:
a = {1,4, name='cq'} setmetatable(a, {__mode='k'}) key = {} a[key] = 'key1' key = {} a[key] = 'key2' print("before GC") for k, v in pairs(a) do print(k, '\t', v) end collectgarbage() print("\nafter GC") for k, v in pairs(a) do print(k, '\t', v) end
輸出:
before GC 1 1 2 4 table: 0x167ba70 key1 name cq table: 0x167bac0 key2 after GC 1 1 2 4 name cq table: 0x167bac0 key2
在本例中,第二句賦值key={}會覆蓋第一個key,當收集器運行時,因爲沒有地方在引用第一個key,所以第一個key就被回收了,而且table中的相應條目也被刪除了。至於第二個key,變量key仍引用着它,所以它沒有被回收。
注意,弱引用table中只有對象能夠被回收,而像數字、字符串和布爾這樣的「值」是不可回收的。
備忘錄(memoize)函數是一種用空間換時間的作法,好比有一個普通的服務器,每當它收到一個請求,就要對代碼字符串調用loadstring,而後再調用編譯好的函數。不過,loadstring是一個昂貴的函數,有些發給服務器的命令有很高的頻率,例如"close()",若是每次收到一個這樣的命令都要調用loadstring,那還不如讓服務器用一個輔助的table記錄下全部調用loadstring的結果。
備忘錄函數的例子:
local results = {} setmetatable(results, {__mode='v'}) function mem_loadstring(s) local res = results[s] if res == nil then res=assert(loadstring(s)) results[s]=res end return res end local a = mem_loadstring("print 'hello'") local b = mem_loadstring("print 'world'") a = nil collectgarbage() for k,v in pairs(results) do print(k, '\t', v) end
例子中,table results會逐漸地積累服務器收到的全部命令及其編譯結果。通過必定時間後,會耗費大量的內存。弱引用table正好能夠解決這個問題,若是results table具備弱引用的value,那麼每次垃圾收集都會刪除全部在執行時未使用的編譯結果。
在lua元表一文中,提到過如何實現具備默認值的table。若是要爲每個table都設置一個默認值,又不想讓這些默認值持續存在下去,也可使用弱引用table,以下面的例子:
local defaults = {} setmetatable(defaults, {__mode='k'}) local mt = {__index=function(t) return defaults[t] end} function setDefault(t, d) defaults[t] = d setmetatable(t, mt) end local a = {} local b = {} setDefault(a, "hello") setDefault(b, "world") print(a.key1) print(b.key2) b = nil collectgarbage() for k,v in pairs(defaults) do print(k,'\t',v) end