由于目前正在负责的项目是一个二次开发项目,而且留给我们的代码质量实在让人无力吐槽,所以遇到了不少大大小小的坑,好在慢慢都淌过去了。最近就遇到了一个内存泄漏的问题,泄漏发生在lua里,项目代码里以前的开发团队留下了检测泄漏的代码,但也仅限于此。由于代码量庞大,所以想从逻辑上梳理清楚哪里的引用没干掉导致了内存泄漏几乎就是大海捞针。好在解决的过程比较顺利,这篇文章就来谈一谈Lua中如何解决内存泄漏的问题。
至于如何发现内存泄漏,也简单说一下,如果是陌生代码,或者虽然是你的,但你也懒得猜哪里泄漏了,那么请参考云风的泄漏检查工具:http://blog.codingnow.com/2012/12/lua_snapshot.html
如果代码的大体逻辑比较熟悉,则可以使用弱引用表来检查是否存在泄漏。通常产生泄漏的都是一些被反复创建的类型,例如游戏里的怪物(打死了就刷一个新的)、玩家(有人登录就要创建一个新的玩家对象),这些东西由于在程序运行周期里反复地创建、销毁,所以一旦有销毁不干净的情况,就容易导致明显的内存泄漏。那么探查这些对象是否存在泄漏,就有一个较为简单的办法,即:弱引用表。(如果你不知道什么是弱引用,请点击这里)。
为了发现内存泄漏,我们可以创建一个全局的弱引用table,使其key为弱引用,然后在每次创建那些可能存在泄漏的对象的时候,都放入这个table,让其作为key,value通常我会用当前时间。由于弱引用的性质,如果其他引用都消失了,那么在弱引用table中对这个对象的引用也会消失(变成nil),反之,只要还有其它任何一个引用存在,这个弱引用表中对这个对象的引用就继续存在。依赖这个特性,当程序已经跑过释放对象的逻辑后,如果这个表中还存在有这个对象的引用,那么这个对象肯定就是泄漏了。
说完了发现泄漏的方法,接下来轮到如何解决。其实我本来也想尝试一下云大的snapshot,奈何这个项目用的是luajit,莫名其妙地不能require,时间紧迫,无法研究,只好作罢,另寻他法。不料一顿google下来除了snapshot之外几乎没有一个像样的解决方案。情急之下,只好研究原理,自己动手,下面请看干货:
既然内存泄漏一定有引用没清,那么基于lua的特性,这个引用一定存在于_G下面的某个table或者function的upvalue中(想不明白这个的同学请想明白再往下看),既然第一步的方法可以定位泄漏,而且还可以得到泄漏对象的引用,那么事情就好办多了。无非就是得到引用之后遍历_G,找到这个引用,并且把层级列出来,方便知道这个东西到底在哪里,想解决就好办的多了。实现方法就不多说了,看代码最直接。下面代码中,调用findObjectInGlobal(泄漏对象的引用),即可找到一切非局部变量的归属关系。
local findedObjMap = nil
function _G.findObject(obj, findDest)
if findDest == nil then
return false
end
if findedObjMap[findDest] ~= nil then
return false
end
findedObjMap[findDest] = true
local destType = type(findDest)
if destType == "table" then
if findDest == _G.CMemoryDebug then
return false
end
for key, value in pairs(findDest) do
if key == obj or value == obj then
_info("Finded Object")
return true
end
if findObject(obj, key) == true then
_info("table key")
return true
end
if findObject(obj, value) == true then
_info("key:["..tostring(key).."]")
return true
end
end
elseif destType == "function" then
local uvIndex = 1
while true do
local name, value = debug.getupvalue(findDest, uvIndex)
if name == nil then
break
end
if findObject(obj, value) == true then
_info("upvalue name:["..tostring(name).."]")
return true
end
uvIndex = uvIndex + 1
end
end
return false
end
function _G.findObjectInGlobal(obj)
findedObjMap = {}
setmetatable(findedObjMap, {__mode = "k"})
_G.findObject(obj, _G)
end