今天我们将针对由Mono分配和管理的托管堆内存,介绍Unity游戏开发中面临的Mono内存管理及泄漏问题。
目前绝大部分Unity游戏逻辑代码所使用的语言为C#,C#代码所占用的内存又称为mono内存,这是因为Unity是通过mono来跨平台解析并运行C#代码的,在Android系统上,游戏的lib目录下存在的libmono.so文件,就是mono在Android系统上的实现。C#代码通过mono解析执行,所需要的内存自然也是由mono来进行分配管理,下面就介绍一下mono的内存管理策略以及内存泄漏分析。
通过上文可知,GC的主要作用在于从已用内存中找出那些不再需要使用的内存,并进行释放。Mono中的GC主要有以下几个步骤:
1.停止所有需要mono内存分配的线程。
2.遍历所有已用内存,找到那些不再需要使用的内存,并进行标记。
3.释放被标记的内存到空闲内存。
4.重新开始被停止的线程。
除了空闲内存不足时mono会自动调用GC外,也可以在代码中调用GC.Collect()手动进行GC,但是,GC本身是比较耗时的操作,而且由于GC会暂停那些需要mono内存分配的线程(C#代码创建的线程和主线程),因此无论是否在主线程中调用,GC都会导致游戏一定程度的卡顿,需要谨慎处理。另外,GC释放的内存只会留给mono使用,并不会交还给操作系统,因此mono堆内存是只增不减的。
![Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客 Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客](http://img1.ph.126.net/ySgX7Mhm9uuQWlamW7y27w==/6631478983050333013.png)
如上图所示,假设A是处于全局数据区的一个对象,那么在GC的时候将作为根节点进行遍历,由于B、C、D对象都可以由A遍历到,因此被标记为活的,E、F对象则没有被标记。注意,由于引用关系是单向的,A引用了B并不代表B也引用了A,所以遍历也只能单向进行。
由于GC以全局数据区和当前寄存器中的对象为根节点进行遍历,所以对象的被标记意味着该对象可以通过全局对象或者当前上下文访问到,而没有被标记的对象则意味着该对象无法通过任何途径访问到,即该对象“失联”了,GC最终会将所有“失联”的对象内存进行回收,上图中的E和F将会在GC过程中被回收。
解决办法
![Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客 Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客](http://img0.ph.126.net/uPmZxI7RS1X_iN0fAxSW6g==/6631536157654976376.png)
![Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客 Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客](http://img2.ph.126.net/PE0B4ojqUsRC3Z_Fih49Mg==/6631592232747992177.png)
![Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客 Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客](http://img0.ph.126.net/mbzljZkZHRqv7_kJKR9yIQ==/6631536157654976394.png)
![Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客 Unity游戏Mono内存管理与泄漏 - IQ007伟哥 - IQ007伟哥的博客](http://img1.ph.126.net/fGpOXC0UOAAiTrPtaD1mHQ==/6631524063027067264.png)
如果需要生成的new int[1000]对象被回收怎么做呢?很简单,将objectA.a设置为null,没有了objectA对其的引用,自然会被GC回收了。需要说明的是,将objectA.a设置为null只是断绝了引用关系,真正对象的回收要等到GC的时候才会进行,Cube在获取内存快照的时候会首先进行一次GC,防止由于没有及时调用GC导致的误判。
腾讯游戏品质管理团队专门打造的工具“Cube”目前已经可以使用,“Cube”可以帮助开发者发现Unity手游内分类资源的占用情况,尤其是对Unity游戏场景中的FPS、CPU、PSS的变化趋势重点关注,帮助在Unity游戏开发过程中不断改善玩家的体验。目前功能免费开放中。