关于 Unity 项目中的 Mono 堆内存泄露

题记:这是补一篇应该在将近一年前就应该写的记录,今天终于补上。

内存泄露是一个老话题了,之前我专门写过一篇 排查 Lua 虚拟机内存泄露 的文章,并且附带了一个工具来查找 Lua 中具体的内存泄露。但是这只是整个 Unity 项目中内存泄漏的一小部分,C# 代码中一般内存泄露可能会更加严重。

我们之前发现无论在 Profiler 还是工具测试,随着战斗的增加,总体内存都是一直在增长,很明显是有了内存泄露。为了首先能够彻底检测到底是哪里出现了泄露,找了很多的工具,也参看了很多的文章,最终感觉首要的问题是需要完整的 C# 内存的快照明细,Mono 提供了一个过时的 HeapShot,看起来就是做这件事情的,但是离直接拿到项目里去使用还有不少的距离。在最后快要放弃打算自己 Hook Mono 的 API 编写时,发现了腾讯的 WeTest Cube 工具。其中有一项是专门用来打印详细内存快照的功能,根据自己的需要来选取内存快照,并且给出测试期间的整体内存走势,使用也很简单,在需要测试的手机上安装 WeTest Cube 软件(注意:手机必须要 Root),然后通过 Cube 里面选择设置,然后启动游戏即可。

启动后在想要打印内存快照的地方,点击屏幕上的 "快照" 按钮,就会在后台生成快照,多点几份然后再双击该按钮退出测试,然后上传数据完成,最后生成的快照数据需要再从 Cube 的网页报告端下载。

由于游戏项目较大,下载后的快照解压缩后基本都在 40+Mb,内容是 json 格式的文本,存取了每一项内存数据的地址,类型,引用链:

{"ptr":-1721623792,"type":"Texture2D","size":16,"stack":"|UnityEngine.GUIStyleState:ProduceGUIStyleStateFromDeserialization (UnityEngine.GUIStyle,intptr)|UnityEngine.GUIStyle:InternalOnAfterDeserialize ()|UnityEngine.GUIUtility:GetDefaultSkin ()|UnityEngine.GUI:DoSetSkin (UnityEngine.GUISkin)|UnityEngine.GUI:set_skin (UnityEngine.GUISkin)|UnityEngine.GUIUtility:BeginGUI (int,int,int)","reference":"|-1721577968"}

ptr - 内存地址
type - 数据类型
size - 内存大小
stack - 该内存被分配时的调用堆栈
reference - 被引用的地址,如果这里有多个,就是被多个其它的地址引用了

所以,依然在主城中打印一份快照,进入战斗打印一份快照,再回到主城打印一份快照,自己编写一个小工具解析两份主城的快照,并且将第二份新增的部分输出成一个单独的文件;然后再编写一个小工具将新增部分根据数据类型 type 来归类,将同类型的 size 相加,最后生成一个文件,里面两列:类型大小。然后用 Excel 打开并按照 大小 降序排列,便可以直接看到那些新增的内存,根据项目情况分析新增部分从 类型大小 两个角度来讲是否应该出现就立即分析出潜在的泄露,然后把泄露的 类型 拿到原始的快照(第二次主城快照)中去搜索,然后查看该类型到底被什么地方引用,一直追溯下去,便能找到最终的引用点,说明是因为“它”的引用才造成泄露。

接下来的事情就是去项目中实际分析代码,查看创建和释放的地方是否有纰漏,需要比较耐心的去项目代码中查找和分析,其实这都是苦活,脏活儿,要的都是耐心,你需要从成千上万条数据中揪出可能的泄露。

后来做完这次优化,我再次感受到,出现这些泄露的原因归根结底还是代码不够规范引起的,不注重必要的初始化和释放成对调用等,大致总结起来有以下几点:

  • 错误使用了过多的 readonly,并且引用了其他的类型,甚至项目引用,最终形成引用环,并且环的某一个节点又被全局对象引用,造成“蝌蚪的尾巴被拴住”的情况,因此连带了很多的对象无法释放。
  • 每一个类应该是有 Initialize 就有 Release,但实际情况大多是有Initialize 而没有 Release
  • 注意 Coroutine,如果使用了全局的 MonoBehaviour 对象 AStartCoroutine 其他对象 B 的函数,那么这时 B 对象会被 A 对象引用,如果协程为正确执行结束在无线等待,那么 B 也会永远被 A 对象引用而不得释放,即使 StopAllCoroutine 也不行。
  • 慎用 Delegate,这时大家经常使用的功能,但是也容易造成内存泄露,如果只是 += 而没有在合适的地方 -=,那么代理的方法所属的对象是会被一直引用而无法释放的。

总结起来很简单,这次优化就大致发现这几条,但是都不经意间,日积月累,引起了比较严重的问题,所以不要小看代码规范,和框架的遵守。

下面是项目优化前后的内存走势,总时长相同。

  • 优化前:

  • 优化后:

以上可以看出,经过一轮粗犷优化后内存依然保持小幅的总体增长,但是比优化前的总体大小已经大幅降低,并且涨幅也更加平缓。接下来可能会需要更大的精力和更多的手段去查找剩下的“一点点”内存泄露。

关于 Unity 项目中的 Mono 堆内存泄露的更多相关文章

  1. javascript中的栈堆内存

    <--------栈内存---------> 俗称叫做作用域(全局作用域/私有作用域) >为js代码提供的执行环境(执行js代码的地方) >基本数据内省是直接存放在栈内存中的 ...

  2. 关于Unity项目中创建项目遇到的一些问题

    1.Unity调用Android的方法默认不是在UI线程执行,所以在Android上写一些页面的重绘的方法,让Unity去调用时,注意要在Android中添加对应的runOnUiThread才可以: ...

  3. Android中使用Handler造成内存泄露的分析和解决

    什么是内存泄露?Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用所指向 ...

  4. Android中使用Handler造成内存泄露

    1.什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用 ...

  5. Android 中 Handler 引起的内存泄露

    在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.其实这可能导致内存泄露,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下.http://w ...

  6. Android中Handler引起的内存泄露

    在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. 1 2 3 4 5 6 7 8 9 public class SampleActivit ...

  7. 【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便

    Tomcat Xms Xmx PermSize MaxPermSize 区别 及 java.lang.OutOfMemoryError: PermGen space 解决 解决方案 在 catalin ...

  8. Unity项目中的资源管理

    这是我在2017金山技术开放日分享的部分内容.从贴图资源格式配置的介绍开始,引申出资源配置工具,最后再谈谈一整套项目资源管理方案.在GitHub上可以获取到资源配置工具的代码,是基于下面理念的一份简单 ...

  9. Unity 项目中委托Delegate用法案例

    Unity中Delegate的用法场景 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar - ...

随机推荐

  1. centos7 网络问题

    1虚拟机网卡设置--网卡设置模式为NET模式(正常情况是可以直接上网的)2“cd /etc/sysconfig/network-scripts/3使用命令“sudo vi ifcfg-ens33”进入 ...

  2. 解决VS Code编译调试中文输出乱码

    最近尝试用VS Code配置了C和C++的编译调试环境,结果遇到了中文输出乱码问题,查阅网上竟然还没有相关问题,有怀疑是mingw中文支持问题,但最后证明是VS Code编码问题. 解决方案: 文件- ...

  3. Idea工具常用技巧总结

    转自:https://www.jianshu.com/p/131c2deb3ecf Idea常用技巧总结 1.无处不在的跳转 注:这里的快捷键是自己定义的,并非大家的都一样,可以通过findActio ...

  4. WIN7 系统 右键计算机 点击管理 出现对话框:找不到文件。

    解决方法: WIN+R组合键运行 “regedit” HKEY_LOCAL_MACHINE----SOFTWARE----Classes----CLSID----{20D04FE0-3AEA-1069 ...

  5. GridControl详解(八)菜单

    菜单控件 拖入窗口中 显示如下 设置popupMenu 设置barManager 设置controller 增加菜单项 弹出配置窗口 一般菜单项设置 对应属性如下: 对应事件: 选择菜单项设置 事件同 ...

  6. 【BZOJ】2820: YY的GCD

    [题意]给定N, M,求1<=x<=N, 1<=y<=M且gcd(x, y)为质数的(x, y)有多少对.T<=10^4,N,M<=10^7. [算法]数论(莫比乌 ...

  7. 【BZOJ做题记录】07.07~?

    在NOI一周前重开一个坑 最后更新时间:7.08 07:38 7.06 下午做的几道CQOI题: BZOJ1257: [CQOI2007]余数之和sum:把k mod i写成k-k/i*i然后分段求后 ...

  8. Hash破解神器:Hashcat的简单使用

    Hash破解神器:Hashcat的简单使用 2014-06-10 21:02:42|  分类: 离线密码破解 |  标签:密码字典  rar密码破解  zip密码破解  密码破解  |举报|字号 订阅 ...

  9. keypress 、keydown、keyup后触发回车

    1.keypress .keydown.keyup的区别 keypress表示键盘按下的全过程,只有按下任意字母数字键(后退.删除等系统功能键无效)时才触发,捕获到的keyCode区分大小写 keyd ...

  10. SurfaceFlinger 讲解

    SurfaceFlinger是Android multimedia的一个部分,在Android 的实现中它是一个service,提供系统 范围内的surface composer功能,它能够将各种应用 ...