前段时间在android环境中使用lua,采用的luabridge是luajava,遇到一个内存泄露问题,查了很久,终于定位了;

场景是这样的:
1:在lua中创建了一个java对象,将jobject指针传递给java;对应在luajava中,即传递了一个luaobject给java;java保存了这个对象;
2:在Lua中需要对这个java对象设置一个事件回调,比如说为某个控件setOnClickListener;
其中设置的代码是这样的:

local viewclick_cb={}
function viewclick_cb.onClick(v)
    self:cb(v)  
end
local listenerProxy  = luajava.createProxy('android.view.View$OnClickListener',viewclick_cb)       
javaobjhandler:setOnClickListener(ListenerProxy)   

lua和java交互时,对象生命周期管理分两种情况:

1:java对象传递个lua虚拟机,lua虚拟机为java对象创建一个userdata,在globalref中增加一个引用,标记这个java对象正在被使用;同时,为这个userdata设置一个__gc元方法,当lua对象需要被释放时,_gc元方法回调,释放掉java对象的globaref;

2:lua对象传递个java虚拟机,将lua对象放到LUA_REGISTRYINDEX中,调用luaL_ref得到一个引用交给一个java对象;在这个java对象的finalize中调用luaL_unref释放引用;

单独看,都没有问题,但当这两个策略同时作用时,就可能产生循环引用;
比如上述代码中,回调函数中self:cb(v)与上下文相关的场景;
java虚拟机和lua虚拟机采取的都是mark-sweep策略,其mark方式都是根节点标记法;两个虚拟机都认为对象存在引用,导致内存泄露; 这个内存泄露问题在其他语言环境中也应该存在;

最后采取的解决方式,采取云风大神这篇文章的思路:
事件回调通过消息机制而不是直接引用的方式,回调函数保存在一个全局weak table中;回调函数关联在一个lua对象中,和lua对象生命周期保持一致;回调事件发生时,java通过一个全局的weak table查询回调函数;

Comments