传统的内存管理策略主要分为两种:引用计数,和垃圾回收。相比后者每一段时间执行一次回收周期,前者是对于每一个变量都维护被引用数的策略。对于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 = nil
print(cacheTable[1]) --table: 000001F3334CAD40
collectgarbage() --zhangsan
print(cacheTable[1]) --nil
collectgarbage() --lisi
Weak表
参考之前的博客:Lua weak表-CSDN博客
collectgarbage函数
显式调用函数以使得Lua直接执行回收相关的操作。