lua代码格式化工具_游戏开发:Unity中Lua造成的堆内存泄露问题

这是侑虎科技第659篇文章,感谢作者唐崇供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者知乎:https://www.zhihu.com/people/ho1dthedoor,同时作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!


起因

上半年项目开始使用UWA GOT Online进行性能分析检测。在Lua项的检查中,引用已经被Destroyed的Unity Object,以致数量一直在上升,由此判断,项目中Lua的使用存在造成C#堆内存泄漏的问题。

d931af00c65c215335946fa73b21d899.png

问题分析与应对

项目采用的热更新方案是ToLua,ToLua给C#对象分配ID存在一个字典里(objectsBackMap),Lua层通过ID访问对应的对象。

当Unity的Object被销毁时,并没有机会通知到Lua。此时,如果引用该对象的Lua变量没有通过LuaGC掉(LuaGC会通知ToLua的字典清理对应数据),则这个已经被Destroy的对象就一直被引用住了。项目中的Lua变量没有被LuaGC掉的情况有以下几种情况:

情况一:Lua对象是全局变量,直接放在_G中。

举例:

button = GameObject.Find("LoginButton")

应对方法:

禁止定义全局变量,给现有的全局变量前加载local声明。可以使用一些Lua静态语法检查的手段,如Luacheck(https://github.com/mpeterv/luacheck)来检查。

情况二:Lua对象被一些全局的Table引用。

我们每个UI面板都对应MVC结构,用了面向对象的概念。其中view在面板关闭时会直接置空,但Ctrl和Model都不会,它们都放在一个全局的管理类(Table)。当Model中持有了面板上的对象时,会出现对象销毁了,但Model中的变量不为空的情况。

举例:

-- login 对象放在全局持有的UI对象管理器中-- UI面板使用mvc结构,在UI销毁时,login的view字段会被赋值为空,而ctrl,model不会。login.model.button = GameObject.Find("LoginButton")

应对方法:

将持有C#对象的变量,定义在会赋值为空的对象中,可以将示例中的代码改为:

login.view.button = GameObject.Find("LoginButton")

情况三:Lua对象的function字段被赋值给了C#的事件/委托。

比如UI控件的按钮点击事件。在LuaGC时,发现C#对象对其有引用,GC不掉。导致Lua中的对象通过Tolua引用住了C#对象,而C#对象又通过ToLua引用Lua对象。

举例:

--UGUI的Button组件提供了onClick事件login.view.loginButton = GameObject:Find("LoginButton"):GetComponent("UntiyEngine.UI.Button")login.view.onLoginButtonClicked = function()-- 处理loginButton点击后的逻辑endlogin.view.loginButton.onClick:AddListener(login.view.onLoginButtonClicked)

应对方法:

(1)对于每一个提供给Lua注册事件/委托的C#类,都继承一个IClear接口,该接口内实现清理事件/委托。

(2)在MonoBehavior的OnDestroy函数内,调用IClear的接口。但要注意的是,这并不能保证所有的组件都是清理完毕,因为deactvie状态的组件,是不会触发OnDestroy的。因此需要手动的调用清理。

(3)提供一个清理GameObject Lua事件/委托的接口,该接口会找到GameObject上所有继承于IClear接口的类,执行清理操作。需要手动清理的GameObject都需要调用该函数。

void ClearGameObject(UnityEngine.GameObject target){    if(target == null) return;    var list = target.GetComponentsInChildren(true);    foreach(var component in list)    {        component.Clear();    }}

(4)提供一个新的Destroy函数全局替换Unity原生的销毁GameObject接口。该函数在做真正销毁前,通过(3)清理所有注册的事件/委托。

验证手段

做完以上修改后,Lua引用已经Destroy对象导致的堆内存泄露问题基本上修复完毕,项目会定期跑UWA  GOT Online的Lua测试进行监控。

UWA会显示并统计已经Destroy对象的数量,而并没有列出具体哪个Lua文件,哪行代码,哪个Lua对象造成了问题。因此,还得有自己的工具来验证和定位问题。

(1)查看是否有引用已经Destroy的对象

Unity重写了UnityEngine.Object类的 Equals方法,如果已经被destroyed的Object equals null 返回true,可以对ToLua的objectsBackMap进行遍历,非空且Equals null的对象,即为已经Destroy的对象。可以将该类对象收集到一个列表中,通过Unity的编辑器代码列出。

(2)查看Lua内存工具

可以从Lua的Registry或者_G开始往下递归查找,找到所有为null userdata的对象(null userdata,在ToLua方案中表示是一个C#对象,并且Equals null)。并且可以反向列出该对象的引用链,直到Registy或_G为止。这样就可以详细的定位是哪个Lua对象造成了问题。具体工具的写法可以参考:https://github.com/yaukeywang/LuaMemorySnapshotDump


文末,再次感谢唐崇的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!

自动化测试正式上线!

a55ad318ce737a23dced8c8c24f585a6.png

近期精彩回顾

【厚积薄发】移动平台纹理压缩格式选择

【学堂上新】游戏开发基础

【厚积薄发】AssetBundle如何计算可靠的Hash值

【博物纳新】基于DOTS的UI解决方案

3d0274a84649c35a396ba7580015d809.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值