垃圾收集机制
Lua manages memory automatically by running a garbage collector to collect all dead objects(that is, objects that no longer accessible from Lua)
- 垃圾收集器GC可以自动执行也可以手动设置
collectgarbage()
- 垃圾收集器GC会自动回收引用计数为0的对象
- 全局声明的对象在Lua中并不认为是垃圾且不会自动被回收,需开发人员手动回收。
- 当手工将对象插入垃圾收集器时,垃圾收集器GC会认为程序引用了此对象,因此并不会自动回收。
Lua的垃圾收集能够回收的对象包括table
、userdata
、function
、thread
、string
,在没有引入弱表前,所有对象引用都是强引用,当对象没有任何引用或仅为弱引用时,对象会被垃圾收集器回收处理。
Lua采用自动内存管理,程序只需要创建对象而无需删除对象,通过垃圾收集机制,Lua会自动删除垃圾对象。Lua垃圾收集器与其它语言不同之处在于,没有环形引用的问题。也就是说,使用到环形数据结构时,无需作任何特殊处理,也能像其他数据结构一样被正常回收。
Lua内存泄漏
Lua中内存泄漏与C/C++中的内存泄露本质上是不一样的,由于Lua拥有垃圾收集机制,理论上是不会存在内存泄漏的问题。因为垃圾收集器会从根部开始扫描所有对象,如果某个地方存在对象引用就不会将此对象的内存进行回收。所以,Lua中的内存泄露指的是那些已经没有被使用但是外部依然还被引用着的对象。
Lua的垃圾收集机制中存在的问题是什么呢?
垃圾收集器只会回收那些它认为是垃圾的对象,而不会回收用户认为是垃圾的对象。典型的例子是栈,栈通常是由一个数组和一个顶部索引来实现的。数组的有效部分总是向顶部扩展的,但是Lua却不知道。如果弹出一个元素时只是递减顶部索引,那么这个仍旧存留在数组中的对象对于Lua而言并不是垃圾。同样对于存储在全局变量中的对象,即使程序不再使用,但对Lua而言它们也不是垃圾。这两种情况中,都需要用户手工将这些变量赋值为nil
,这样才能得以释放内存空间。
另外,简单地清除引用可能并不够。例如,只要对象处于数组中时就无法被回收。因为即使当前没有使用到数组中的对象,但数组仍旧引用着它。除非用户主动告诉Lua这项引用不应该阻碍此对象的回收。
当把对象放入表中时就会产生一个引用,即使其他地方没有对表中的元素有任何引用,垃圾收集器会不会回收这些对象,你只能选择手动释放表元素或是将其常驻内存。
local tbl = {}
local key = {count = 100}
tbl[key] = 1
key = nil
print(tbl.key) -- nil
-- 强制执行垃圾回收
collectgarbage()
for k,v in pairs(tbl) do
print(k, v, k.count)-- table: 00000000001a9b70 1 100
end
弱引用表机制
弱引用&弱表
A weak table is a table whose elements are weak references
用户使用弱表(weak table)来告知Lua一个引用(reference)不应该阻碍一个对象被回收。弱引用(weak reference)是一种会被垃圾收集器(collectgabage)忽视的对象引用。如果一个对象的所有引用都是弱引用,那么Lua就可以回收这个对象了。Lua使用弱表(weak table)来实现弱引用(weak reference),一个弱表就是具有弱引用条目的表。如果一个对象只被弱表所持有,那么Lua会回收这个对象。
弱表(weak table)
- 弱表是一个表且拥有
metatable
元表,并在metatable
中定义了__mode
字段。
一个表的弱引用类型是通过其元素的__mode
模式字段来决定的,__mode
模式的值是一个字符串,如果字符串中包含字母k
则这个表的key
键名是弱引用。如果字符串中包含字母v
则表的value
键值是弱引用的。
__mode
字段取值可分为k
、v
、kv
-
k
表示table.key
是weak
的,也就是table
的keys
是能够被垃圾收集器自动回收。 -
v
表示table.value
是weak
的,也就是table
的values
能够被垃圾收集器自动回收。 -
kv
是二者的组合,任何情况下只要key
和value
中的一个被垃圾收集器自动回收,那么kv
键值对就被从表中移除。– 创建弱表
local tbl = {}
local mt = {__mode = “k”}
setmetatable(tbl, mt)key = {count = 100}
tbl[key] = 1key = {}
tbl[key] = 2– 强制垃圾收集
collectgarbage()for k,v in pairs(tbl) do
print(k, v, k.count) – table: 00000000001f9730 2 nil
end
Lua只回收弱引用table
中的对象,值类型数据如数字和布尔值是不可回收的。不过,字符串再次显得有些特殊,虽然从实现的角度看,字符串是可回收的。但在有些方面,字符串却和其他可回收对象不同。从开发者的角度来看,字符串就是值而非对象。因此,字符串和数字和布尔一样,不会从弱表中删除。
- 弱表中的引用是弱引用,弱引用不会导致对象引用计数变化。换言之,如果一个对象只有弱引用指向它,那么垃圾收集器会自动回收该对象所占用的内存空间。
应用案例
使用“空间换时间”是一种通用的程序运行效率优化手段,例如对于一个普通的服务器,它接收到的请求中包含Lua代码,每当收到请求后都会调用Lua的loadstring
函数来动态解析请求中的Lua代码,如果这种操作过于频繁,会直接导致服务器的执行效率下降。
要解决这个问题,可以将每次解析的结果缓存到一个table
中,下次如果接收到相同的Lua代码则无需调用loadstring
来动态解析,而是直接从table
中获取并直接执行即可。这样,在有大量重复Lua代码的情况下,可极大地提高服务器的执行效率。反之,若有相当一部分的Lua代码只是出现一次,使用这种机制会导致大量的内存资源被占用而得不到有效的释放。在这种情况下,如果使用弱表,不仅仅可以在一定程序上提升程序的运行效率,内存资源也会得到有效的释放。
local result = {}
setmetatable( result, {__mode="v"} )
function mem_loadstring(str)
local val = result[str]
if val==nil then
val = assert( loadstring( str ) )
result[str] = val
end
return val
end