傳統的內存管理策略主要分為兩種:引用計數,和垃圾回收。相比后者每一段時間執行一次回收周期,前者是對于每一個變量都維護被引用數的策略。對于Lua這種輕量化語言而言,占據大內存的開銷是極力避免的,而前者的方式顯然是增加內存開銷的壞主意。因此Lua采取了垃圾回收的機制,這也是本篇文章的主題。
垃圾收集原理
分為兩個階段:標記-清除階段。
標記階段
從根集(全局變量,當前訪問的局部變量等等)開始,依次向內部遞歸標記活動對象。
清除階段
掃描所有對象,沒有被標記的對象都被認為是不再被引用的對象,可以釋放內存。
垃圾收集策略
目前Lua有兩種策略:增量式GC和分代式GC。Lua默認收集策略是前者。
增量式GC
此模式下,每個GC循環都會和主程序交錯運行,并不會在一段時間內完成所有的GC步驟。在此模式下的總GC時間是不會變的,但是相比于之前那種完整的一段時間的卡頓,是要優化不少的。
引入三個參數:收集器頻率、收集器步進乘數、收集器步進。
第一個參數意指內存使用量達到上次垃圾收集后的內存的n%時執行收集操作。默認為200,即達到上次收集的內存兩倍時開始新循環,小于100意味著直接執行收集操作,最大為1000。
第二個參數意指標記/掃描元素的速度。默認值是100,最大值是1000。值越大標記速度越快,但需要的分配內存也會越大。
第三個參數意指執行標記步驟之前分配多少內存。此數字n指的是2^n字節。較大的值可能會使增量式GC轉變成早前版本的非增量式GC。
使用的是三色標記法來實現增量式GC。
如圖所示,分為三個色系。
黑色為被標記且已經被完全檢查引用的對象。
灰色為被標記但是還沒有被完全檢查引用的對象。
白色是暫時還沒有被標記的對象。
我們從根系開始尋找對象,直到不再有灰色對象存在為止(因為灰色對象最終都會轉成黑色)。最終所有白色對象都應當被回收。
增量GC過程中或許會有新的引用關系,因此需要考慮以下兩個問題:
1.已經被標為黑色的對象和白色的對象之間建立了新的聯系怎么辦
2.被標為灰色的對象和之前引用它的黑色對象之間斷開聯系了怎么辦
對于第一個問題,可以通過增量更新的方式解決:插入引用關系的時候,將黑色對象記錄下來,等到標記過程完全結束后,重新掃描這類黑色對象,那些被標記為白色的對象重新成為灰色對象并再次執行以上檢查引用操作。
對于第二個問題,可以通過原始快照的方式解決,也和上面方法類似,記錄下來變動的黑色對象,標記過程結束后重新掃描。當然也可以選擇不理睬,因為這類灰色對象引用斷開后會在下一次GC過程中成為白色對象,依然會被清除。
分代式GC
此模式下,收集器會頻繁進行次要收集,僅遍歷最近創建的對象;次要收集后仍高于內存限制,則進行主要收集,遍歷所有對象。
引入兩個參數:次要收集頻率,主要收集頻率。
這兩個參數都意指在內存使用量超過上次收集后的內存的n%時(注意是超過,增量式GC的頻率值是達到)實行收集策略。前者是超過n%時開始次要收集,默認值是20,最大值是200;后者是超過n%時開始主要收集,默認值是100,最大值是1000。
分為兩種對象:新生代和老年代。其根據對象創建時機判斷(即經歷了多少次垃圾收集)。次要收集主要收集這些新生代,原因在于許多新創建的對象很快就不會再被需要了,而老年代通常會繼續存活下來。
分代式GC的優點在于減少暫停時間,收集效率變高,但同時也因為其復雜性和優先級,會容易使老年代可能占據內存的較長時間。
輔助垃圾收集
有時候Lua并不清楚哪些是我們所認為的垃圾,所以需要我們自身做輔助工作保證Lua完成釋放內存的任務。
析構器
一個對象被回收時,如果其有__gc元方法,則會調用此方法。注意,官方文檔中強調,執行這段元方法時會短暫地復活這個對象,原因是元方法內部可能會有對象自身的調用。但是這是暫時的,如果此元方法沒有改變對象的引用信息(比如這個對象被全局變量引用),那么下一次GC依然會把這個對象給收集掉。還有一點值得注意:如果gc內部又賦值了一次gc元方法,則會在下一次gc時又調用一次gc元方法,參考以下代碼:
t = {name = "zhangsan"}
setmetatable(t,{__gc = function (t)print(t.name)t.name = "lisi"setmetatable(t,{__gc = function (t)print(t.name)end})
end})--用一個weak表管理
local cacheTable = {}
setmetatable(cacheTable,{__mode = 'v'
})
table.insert(cacheTable,t)t = nilprint(cacheTable[1]) --table: 000001F3334CAD40
collectgarbage() --zhangsan
print(cacheTable[1]) --nil
collectgarbage() --lisi
Weak表
參考之前的博客:Lua weak表-CSDN博客
collectgarbage函數
顯式調用函數以使得Lua直接執行回收相關的操作。