关于 Unity 项目中的 Mono 堆内存泄露
关于 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对象A去StartCoroutine其他对象B的函数,那么这时B对象会被A对象引用,如果协程为正确执行结束在无线等待,那么B也会永远被A对象引用而不得释放,即使StopAllCoroutine也不行。 - 慎用
Delegate,这时大家经常使用的功能,但是也容易造成内存泄露,如果只是+=而没有在合适的地方-=,那么代理的方法所属的对象是会被一直引用而无法释放的。
总结起来很简单,这次优化就大致发现这几条,但是都不经意间,日积月累,引起了比较严重的问题,所以不要小看代码规范,和框架的遵守。
下面是项目优化前后的内存走势,总时长相同。
优化前:

优化后:

以上可以看出,经过一轮粗犷优化后内存依然保持小幅的总体增长,但是比优化前的总体大小已经大幅降低,并且涨幅也更加平缓。接下来可能会需要更大的精力和更多的手段去查找剩下的“一点点”内存泄露。
关于 Unity 项目中的 Mono 堆内存泄露的更多相关文章
- javascript中的栈堆内存
<--------栈内存---------> 俗称叫做作用域(全局作用域/私有作用域) >为js代码提供的执行环境(执行js代码的地方) >基本数据内省是直接存放在栈内存中的 ...
- 关于Unity项目中创建项目遇到的一些问题
1.Unity调用Android的方法默认不是在UI线程执行,所以在Android上写一些页面的重绘的方法,让Unity去调用时,注意要在Android中添加对应的runOnUiThread才可以: ...
- Android中使用Handler造成内存泄露的分析和解决
什么是内存泄露?Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用所指向 ...
- Android中使用Handler造成内存泄露
1.什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用 ...
- Android 中 Handler 引起的内存泄露
在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.其实这可能导致内存泄露,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下.http://w ...
- Android中Handler引起的内存泄露
在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. 1 2 3 4 5 6 7 8 9 public class SampleActivit ...
- 【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便
Tomcat Xms Xmx PermSize MaxPermSize 区别 及 java.lang.OutOfMemoryError: PermGen space 解决 解决方案 在 catalin ...
- Unity项目中的资源管理
这是我在2017金山技术开放日分享的部分内容.从贴图资源格式配置的介绍开始,引申出资源配置工具,最后再谈谈一整套项目资源管理方案.在GitHub上可以获取到资源配置工具的代码,是基于下面理念的一份简单 ...
- Unity 项目中委托Delegate用法案例
Unity中Delegate的用法场景 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar - ...
随机推荐
- JS自学大全
JS是从上往下执行的 console.log();输出语句console.warn();错误提示语句 黄色三角形感叹号console.error();错误提示 红色圆Xalert();弹窗docume ...
- JS-this的用法
o.onclick=function(){alert(this)}//这个this是指o ------ var arr=[1,2,3,4,5]; arr.a=12; arr.show=function ...
- RabbitMQ的原理和使用
转载:RabbitMQ从入门到精通 转载:轻松搞定RabbitMQ 转载:RabbitMQ Java入门教程 一.RabbitMQ AMQP,即Advanced Message Queuing Pro ...
- Codeforces 221 D. Little Elephant and Array
D. Little Elephant and Array time limit per test 4 seconds memory limit per test 256 megabytes input ...
- JVM调优总结(3):垃圾回收面临的问题
如何区分垃圾 上面说到的“引用计数”法,通过统计控制生成对象和删除对象时的引用数来判断.垃圾回收程序收集计数为0的对象即可.但是这种方法无法解决循环引用.所以,后来实现的垃圾判断算法中,都是从程序运行 ...
- Windows下搭建网络代理
场景:有些场景下会出现局域网内的某些网段可能由于安全限制,不能访问外网,此时可以通过安装一些工具来实现借助局域网内某些能够上外网的电脑来实现网络代理的功能.以下工具均是使用于Window环境. 服务端 ...
- call_user_func 具体使用方法,实例说明
<?php class Person{ public $name="jack"; public static function say(){ echo "ok&qu ...
- hdu 5373 The shortest problem(杭电多校赛第七场)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5373 The shortest problem Time Limit: 3000/1500 MS (J ...
- webgote的例子(4)Sql注入(SelectGET)
SQL Injection (Select/GET) 本章内容 (查询显示中要注意的错误) 这里面我们看一下 movie的数值,选择表单中的当我们选择的二个的时候 move的值也变成了第二个,选择表单 ...
- Linux下的lds链接脚本详解【转】
转自:http://www.cnblogs.com/li-hao/p/4107964.html 转载自:http://linux.chinaunix.net/techdoc/beginner/2009 ...