1 弱引用表
1.1 弱引用
如果一个对象被引用,那么垃圾回收器不会回收该对象,这就是“强引用”。与“强引用”对应,如果一个对象没有被引用,或者仅被“弱引用”,那么垃圾回收器会忽视弱引用,回收该对象。
1.2 弱引用表
指元素均被“弱引用”的表。
我们无法通过变量直接“弱引用”一个对象,我们需要通过指定一张表为弱引用表,然后将对象添加到表中,通过表来“弱引用”多个对象。
表由键值对组成,一般情况下键和值都是被“强引用”;在一个弱引用表中,键和值都可以是被“弱引用”的,这就意味着有三种类型的弱引用表:具有弱引用键的表、具有弱引用值的表,同时具有弱引用键和值的表,不论是哪种类型的弱引用表, 要有一个键或值被回收了,那么对应的整个键值对都会被从表中删除。
1.3 “弱引用”对象的方法
点击查看代码
--1. 首先我们指定a表为弱引用表
a = {}
--元表.__mode表示该表是否为弱引用表,"k":弱引用键,"v":弱引用值,"kv":弱引用键值
mt = {__mode ="v"}
setmetatable(a, mt) --现在a的值是弱引用的了
--2. 创建一个对象,当前该对象被b“强引用”
local b = "object~~"
--3.向弱引用表添加一个对象
a[1] = b
collectgarbage() --强制执行垃圾回收
print(tostring(a[1])) --> object~~
--4.删除对象的所有强引用,垃圾回收后,该对象被彻底回收,弱引用表也会删除该对象的引用
b = nil
collectgarbage() --强制执行垃圾回收
print(tostring(a[1])) --> nil
2 瞬表
瞬表是一种特殊的弱引用表,一个具有弱引用键和强引用值的表被Lua作为瞬表,它的特殊之处在于:如果瞬表的值引用了键,在不做特殊处理的情况下,即使表外部没有强引用,垃圾收集器也不会回收键。
Lua在5.2中引入瞬表的概念,并做了特殊处理,使得垃圾收集器否认表内部对弱引用键的强引用,正确回收键。Lua5.1依然存在这个问题。
3 析构器
析构器与构造器相似,是对象的一个特殊函数,析构器不会被开发人员主动调用,而是在垃圾收集器回收对象时,被垃圾收集器调用。
3.1 定义析构器
点击查看代码
--通过元方法__gc实现析构器
local o = {x="obj dead."}
setmetatable(o, {__gc = function(o) print(o.x) end}) --析构器的参数正式被析构的对象
o = nil
collectgarbage() --> obj dead.
为对象设置元表时,如果元表定义了析构器,那么垃圾收集器会将对象标记为“需要进行析构处理”,放入特定队列回收对象时,将根据该标记决定是否调用对象的析构器,这保证了每个对象的析构器都会精确地运行且只运行一次。
定义析构器的坑:
为对象设置的元表没有析构器,即使后续向元表增加__gc元方法,垃圾收集器也不会做任何处理。
点击查看代码
local o = {x="obj dead."}
local mt = {}
setmetatable(o, mt) --元表没有定义析构器
mt.__gc = function(o) print(o.x) end --追加定义析构器
o = nil
collectgarbage() --> print nothing --追加定义的析构器没有被调用
3.2 复苏
当垃圾回收处于析构阶段,由于析构器的参数正式被析构的对象,因此,该对象及其字段引用的其他对象,会在析构期间重新活跃,这意味着,上述例子中,在o的析构器调用前,不能释放x的内存。
4 垃圾收集器
Lua 语言通过垃圾收集器(garbage collector)自动地删除成为垃圾的对象。
Lua语言使用的是一个简单的标记-清除(mark-and-sweep)式垃圾收集器,这种收集器又被称为“全局暂停(stop-the-world)”式的收集器,意味着垃圾收集器在需要时,会中断主程序的运行来执行一次完整的垃圾收集周期, 每一个垃圾收集周期由四个阶段组成:标记(mark)、清理(cleaning)、清除(sweep)、析构(finalization)。
标记:当对象被变量或其他对象的字段引用时,意味着该对象是程序可达的,垃圾收集器将这样的对象标记为活跃。(垃圾收集器不会标记被弱引用的对象)
清理:针对弱引用表,清理非活跃对象;针对需要执行析构器的非活跃对象,将对象放入析构列表,该列表会在析构阶段用到。
清除:上一次GC中需要回收的对象,在这一次GC的清除阶段释放其内存。
析构:遍历析构列表,按照早前“需要进行析构处理”标记的标记顺序,调用对象的析构器,这个阶段没有释放任何对象的内存,析构器内可以安全地访问任何对象成员。在下一次GC才会将本次GC析构完成的对象从内存中删除。这意味着,如果想保证我们程序中的所有垃圾都被真正释放,那么必须调用collectgarbage两次,第二次调用才会释放第一次调用中被析构的对象内存。
以上是Lua5.0的垃圾收集器工作方式,后续Lua对其进行了优化。
Lua5.1使用了增量式垃圾收集器(incremental collector),这种垃圾收集器像老版的垃圾收集器一样执行相同的步骤,但是不需要在垃圾收集期间停止主程序的运行,它与主程序交替运行。
Lua5.2引入了紧急垃圾收集(emergency collection)。当内存分配失败时,Lua会强制执行一次完整的垃圾收集,然后再尝试分配。(此处完整的垃圾收集会释放本次GC中析构完成的对象,不再留给下次GC)
5 控制垃圾收集器
通过函数collectgarbage可以对垃圾收集器做些额外的控制:
collectgarbage ([opt [, arg]])
第一个参数是一个可选字符串,指定对垃圾收集器做何种控制;第二个参数是对指定的控制补充额外参数。
collectgarbage("collect"): 做一次完整的垃圾收集循环。
collectgarbage("count"): 返回当前已使用的内存大小,以KB为单位。
collectgarbage("restart"): 重启垃圾收集器。
collectgarbage("stop"): 停止垃圾收集器,直到调用collectgarbage("restart")
collectgarbage("setpause"): 将 arg 设为收集器的间歇率。 返回旧的间歇率值。
collectgarbage("setstepmul"): 将 arg 设为收集器的步进倍率。 返回旧的步进倍率值。