WeTest导读
内存是游戏的硬伤,如果没有做好内存的管理问题,游戏极有可能会出现卡顿,闪退等影响用户体验的现象。本文介绍了在腾讯游戏在Unity游戏开发过程中常见的Mono内存管理问题,并介绍了一系列解决的策略和方法。

什么是Mono内存
对于目前绝大多数基于Unity引擎开发的项目而言,其托管堆内存是由Mono分配和管理的。“托管” 的本意是Mono可以自动地改变堆的大小来适应你所需要的内存,并且适时地调用垃圾回收(Garbage Collection)操作来释放已经不需要的内存,从而降低开发人员在代码内存管理方面的门槛。
Unity游戏在运行时的内存占用情况可以用下图表示:

目前绝大部分Unity游戏逻辑代码所使用的语言为C#,C#代码所占用的内存又称为mono内存,这是因为Unity是通过mono来跨平台解析并运行C#代码的,在Android系统上,游戏的lib目录下存在的libmono.so文件,就是mono在Android系统上的实现。C#代码通过mono解析执行,所需要的内存自然也是由mono来进行分配管理,下面就介绍一下mono的内存管理策略以及内存泄漏分析。

Mono内存管理策略
Mono通过垃圾回收机制(Garbage Collect,简称GC)对内存进行管理。Mono内存分为两部分,已用内存(used)和堆内存(heap),已用内存指的是mono实际需要使用的内存,堆内存指的是mono向操作系统申请的内存,两者的差值就是mono的空闲内存。当mono需要分配内存时,会先查看空闲内存是否足够,如果足够的话,直接在空闲内存中分配,否则mono会进行一次GC以释放更多的空闲内存,如果GC之后仍然没有足够的空闲内存,则mono会向操作系统申请内存,并扩充堆内存,具体如下图所示。

通过上文可知,GC的主要作用在于从已用内存中找出那些不再需要使用的内存,并进行释放。Mono中的GC主要有以下几个步骤:
1.停止所有需要mono内存分配的线程。
2.遍历所有已用内存,找到那些不再需要使用的内存,并进行标记。
3.释放被标记的内存到空闲内存。
4.重新开始被停止的线程。
除了空闲内存不足时mono会自动调用GC外,也可以在代码中调用GC.Collect()手动进行GC,但是,GC本身是比较耗时的操作,而且由于GC会暂停那些需要mono内存分配的线程(C#代码创建的线程和主线程),因此无论是否在主线程中调用,GC都会导致游戏一定程度的卡顿,需要谨慎处理。另外,GC释放的内存只会留给mono使用,并不会交还给操作系统,因此mono堆内存是只增不减的。

Mono内存泄漏分析
Mono是如何判断已用内存中哪些是不再需要使用的呢?是通过引用关系的方式来进行的。Mono会跟踪每次内存分配的动作,并维护一个分配对象表,当GC的时候,以全局数据区和当前寄存器中的对象为根节点,按照引用关系进行遍历,对于遍历到的每一个对象,将其标记为活的(alive)。

如上图所示,假设A是处于全局数据区的一个对象,那么在GC的时候将作为根节点进行遍历,由于B、C、D对象都可以由A遍历到,因此被标记为活的,E、F对象则没有被标记。注意,由于引用关系是单向的,A引用了B并不代表B也引用了A,所以遍历也只能单向进行。

由于GC以全局数据区和当前寄存器中的对象为根节点进行遍历,所以对象的被标记意味着该对象可以通过全局对象或者当前上下文访问到,而没有被标记的对象则意味着该对象无法通过任何途径访问到,即该对象“失联”了,GC最终会将所有“失联”的对象内存进行回收,上图中的E和F将会在GC过程中被回收。

既然mono已经有了完善的GC机制,那是否还会存在内存泄漏呢?答案是肯定的,只是此处的内存泄漏需要重新定义一下,我们把对象已经不再需要使用却没有被GC回收的情况称为mono内存泄漏。Mono内存泄漏会使空闲内存减少,GC频繁,mono堆不断扩充,最终导致游戏内存占用的升高。下图就是一个mono内存泄漏的例子。


解决办法
对于mono内存泄漏,一般只能通过猜测+不断修改代码测试的方法来修复问题,效率很低,腾讯Wetest平台的Cube工具提供了mono内存快照对比的功能,并包括对象分配堆栈,对象引用关系等详细信息,是定位mono内存泄漏问题的一大利器。下面结合具体的代码尝试使用Cube定位mono内存泄漏问题。
首先我们定义类A,并在A的构造函数中申请了一块int[1000]大小的内存。

接着我们定义A类型的静态变量objectA,在游戏界面上绘制一个按钮,并在按钮点击事件中给objectA赋值,此时新生成了new int[1000]对象,并由objectA引用。

使用Cube的mono内存检测功能,并在按钮按下之前和按下之后分别进行一次快照,对比两次快照,查看快照间新增对象。

可以看到,按钮按下前后新增的最大对象即为代码中生成的new int[1000]对象,并且该对象被引用的次数为1,为了查看详细的引用关系,下载快照文件snapshot2,其中有这样两行数据:

第一行说明在OnGUI函数中生成了一个A类型的对象,其指针为1533098928,第二行说明在OnGUI()->A:.cotr()中生成了一个Int32[]类型的对象,并且该对象被指针为1533098928的对象引用。即new int[1000]对象被objectA引用,这也是导致new int[1000]对象无法被GC回收的原因。而objectA本身是一个静态对象,是GC的根节点,因此没有对象引用。
如果需要生成的new int[1000]对象被回收怎么做呢?很简单,将objectA.a设置为null,没有了objectA对其的引用,自然会被GC回收了。需要说明的是,将objectA.a设置为null只是断绝了引用关系,真正对象的回收要等到GC的时候才会进行,Cube在获取内存快照的时候会首先进行一次GC,防止由于没有及时调用GC导致的误判。

游戏中大部分mono内存泄漏的情况都是由于静态对象的引用引起的,因此对于静态对象的使用需要特别注意,尽量少用静态对象,对于不再需要的对象将其引用设置为null,使其可以被GC及时回收,但是由于游戏代码过于复杂,对象间的引用关系层层嵌套,真正操作起来难度很大。可以首先使用Cube工具进行分析,根据mono内存趋势找出泄漏的具体场景,然后再使用快照对比功能进行详细分析。

版权声明:本文为CSDN博主「腾讯WeTest」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wetest_tencent/article/details/52130703

Unity 之 Mono内存管理与泄漏 — 内存是手游的硬伤(转)相关推荐

  1. 内存是手游的硬伤——Unity游戏Mono内存管理与泄漏

    测试开发者的共同关注! WeTest导读 内存是游戏的硬伤,如果没有做好内存的管理问题,游戏极有可能会出现卡顿,闪退等影响用户体验的现象.本文介绍了在腾讯游戏在Unity游戏开发过程中常见的Mono内 ...

  2. C++:内存管理:C++内存管理详解

    C++语言内存管理是指:对系统的分配.创建.使用这一系列操作.在内存管理中,由于是操作系统内存,使用不当会造成很麻烦的后果.本文将从系统内存的分配.创建出发,并且结合例子来说明内存管理不当会造成的结果 ...

  3. 属性与内存管理(属性与内存管理都是相互关联的)

    <span style="font-size:18px;"> 属性与内存管理(属性与内存管理都是相互关联的)第一部分一,属性:属性是OC2.0之后出来的新语法,用来取代 ...

  4. 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )

    文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...

  5. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  6. Java内存管理:Java内存区域 JVM运行时数据区

    Java内存管理:Java内存区域 JVM运行时数据区 在前面的一些文章了解到javac编译的大体过程.Class文件结构.以及JVM字节码指令. 下面我们详细了解Java内存区域:先说明JVM规范定 ...

  7. 鸿蒙系统内存管理,嵌入式系统内存管理-鸿蒙HarmonyOS技术社区-鸿蒙官方战略合作伙伴-51CTO.COM...

    1.概述 操作系统的内存管理功能用于向操作系统提供一致的地址映射功能和内存页面的申请.释放操作.在嵌入式实时系统中,内存管理根据不同的系统,有不同的策略,对于有些系统支持的虚拟内存管理机制,对于另外一 ...

  8. 【C 语言必知必会】内存管理、动态分配内存、野指针

    C 语言内存管理.动态分配内存.野指针 文章目录 C 语言内存管理.动态分配内存.野指针 前言: 1.内存分区 1.1 代码区 1.2.1 全局初始化数据区(静态数据区data段) 1.2.2 未初始 ...

  9. [JAVA]第二篇(内存管理,HashMap内存泄漏解决办法)

    网上看到一个关于内存泄漏处理的例子,原网址:http://www.jb51.net/article/49428.htm,下面笔者将具体分析下这篇文章中的代码,并从中学习JAVA的内存管理. (Begi ...

最新文章

  1. 可变数组集合ArrayList
  2. linux线程时间片是多少_Linux 线程的实质
  3. python 输出 GPU内存 最大使用率
  4. 如何更改电脑ip地址租期_局域网通过IP地址如何找到电脑的位置
  5. Java:Swing篇,实现JList、JTextArea的自动滚动,实时刷新功能
  6. h5+js调取相机做取景框_iPhone 12 相机操作指南,用好了随手一拍就是大片!
  7. python——字典攻击
  8. 如何防止135端口入侵
  9. javascript特效
  10. 世界首块10层3D打印PCB电路板问世!
  11. 计算机课怎么管纪律,作为班主任,班级纪律应该怎么管?丨班级圆桌派
  12. db2导出适用于mysql的数据_db2数据库导出导入数据库
  13. 【个人总结】 2016年个人年度总结
  14. 电脑重装系统后台式电脑网卡坏了怎么修复
  15. word 编辑过程中变为只读_word文档保存后,如何恢复之前的资料?
  16. python读取大文件md5校验性能优化比较
  17. 日本超高人气聊天软件LINE最全注册攻略来了
  18. Android下拉筛选DropDownMenu
  19. 医疗器械标准目录 第二部分专业技术领域(1)
  20. 安装pyinstaller报错----Fatal error in launcher:

热门文章

  1. linux C编程8-字符串处理
  2. 原子吸收光谱AAS的原理与应用
  3. win11修改ip属地
  4. FAT12中文件目录项时间、日期对应字节问题
  5. eclipse中执行main函数显示错误:找不到或无法加载主类
  6. 影视网站排名,关键词SEO注意事项
  7. java一个类添加多个接口_Java的一个类可以实现多个接口。
  8. 【财务危机】--2018.6债务
  9. 5e服务器显示fps被锁定,5E掉帧严重 别给我说是我硬件问题
  10. laydate中的默认日期选择及 date中如何选择下个月整月的时间