Unity内存申请和释放
转自:http://www.jianshu.com/p/b37ee8cea04c
1.资源类型
GameObject, Transform, Mesh, Texture, Material, Shader, Script和各种其他Assets。
2.资源创建方式
- 静态引用,在脚本中加一个public GameObject变量,在Inspector面板中拖一个prefab到该变量上,然后在需要引用的地方Instantiate;
- Resource.Load,资源需要放在Assets/Resources目录下;
- AssetBundle.Load, Load之后Instantiate。
3. 资源销毁方式
- GameObject.Destroy(gameObject),销毁该物体;
- AssetBundle.Unload(false),释放AssetBundle文件内存镜像,不销毁Load创建的Assets对象;
- AssetBundle.Unload(true),释放AssetBundle文件内存镜像同时销毁所有已经Load的Assets内存镜像;
- Resources.UnloadAsset(Object),释放已加载的Asset对象;
- Resources.UnloadUnusedAssets,释放所有没有引用的Asset对象。
4. 生命周期
实验篇
实验中创建了一个简单场景,场景中创建了一个Empty GameObject,上面挂了一个脚本,在Awake函数中通过协程函数来创建资源,具体的Coroutine函数下面都有。
实验中创建的Prefab是一个坦克车,加入场景中场景内存增加3M左右,同时创建了一个AssetBundle资源供AssetBundle使用。1. Resources.Load方式加载一个Prefab, 然后Instantiate GameObject
代码如下:
IEnumerator LoadResources()
{
// 清除干净以免影响测试结果
Resources.UnloadUnusedAssets();
// 等待5秒以看到效果
yield return new WaitForSeconds(5.0f);
// 通过Resources.Load加载一个资源
GameObject tank = Resources.Load("Role/Tank") as GameObject;
yield return new WaitForSeconds(0.5f);
// Instantiate一个资源出来
GameObject tankInst = GameObject.Instantiate(tank, Vector3.zero, Quaternion.identity) as GameObject;
yield return new WaitForSeconds(0.5f);
// Destroy一个资源
GameObject.Destroy(tankInst);
yield return new WaitForSeconds(0.5f);
//释放无用资源
tank = null;
Resources.UnloadUnusedAssets();
yield return new WaitForSeconds(0.5f);
}
执行结果如下:
下面是统计结果:
| 数据描述 | Memory | Texture | Mesh | Material | GameObjects | Objects in Scene | Total Objects |
|---|---|---|---|---|---|---|---|
| 初始 | 72.8M | 1271/8.0M | 35/223.0K | 25/10.2K | 7 | 211 | 2187 |
| Resources.Load | 72.8M | 1271/8.0M | 36/0.8M | 25/10.2K | 7 | 211 | 2280 |
| Instantiate | 75.3M | 1272/9.3M | 36/0.8M | 26/10.7K | 52 | 303 | 2375 |
| Destroy | 74.7M | 1272/9.3M | 36/0.8M | 26/10.7K | 7 | 211 | 2283 |
| Resources.UnloadUnusedAssets | 72.3M | 1271/8.0M | 35/223.0K | 25/10.2K | 7 | 211 | 2187 |
从这里我们得出如下结论:
- Resouces.Load一个Prefab相对于Instantiate一个资源来说是相对轻量的一个操作,上述过程中,Resources.Load加载一个Prefab几乎没有消耗内存,而Instantiate消耗了2.5M的资源空间。Resources.Load增加了Mesh和Total Object的数量,而Instantiate增加了GameObjects,Objects In Scene和Total Objects的数量;
- Destroy一个GameObject之后,内存有所减少,但是比较少,本例中减少了0.6M;Instantiate和Destroy前后Material和Texture没有还原,用以后面继续进行Instantiate之用。
若没有调用Resources.UnloadUnusedAssets,则结果如下:

统计结果如下:
| 数据描述 | Memory | Texture | Mesh | Material | GameObjects | Objects in Scene | Total Objects |
|---|---|---|---|---|---|---|---|
| 初始 | 58.9M | 1258/7.5M | 34/219.2K | 22/9.0K | 7 | 117 | 2078 |
| Resources.Load | 60.0M | 1258/7.5M | 35/0.8M | 22/9.0K | 7 | 117 | 2171 |
| Instantiate | 62.5M | 1259/8.9M | 36/0.8M | 23/9.5K | 52 | 209 | 2256 |
| Destroy | 61.8M | 1259/8.9M | 35/0.8M | 23/9.5K | 7 | 117 | 2174 |
得出如下结论:
如果不手动执行Resources.UnloadUnusedAssets,则多余的Mesh,Material和Object不会主动释放。
2. 以AssetBundle.Load的方式加载一个Prefab,然后Instantiate一个GameObject
代码如下:
IEnumerator LoadAssets(string path)
{
// 清除干净以免影响测试结果
Resources.UnloadUnusedAssets();
// 等待5秒以看到效果
yield return new WaitForSeconds(5.0f);
// 创建一个WWW类
WWW bundle = new WWW(path);
yield return bundle;
yield return new WaitForSeconds(0.5f);
// AssetBundle.Load一个资源
Object obj = bundle.assetBundle.Load("tank");
yield return new WaitForSeconds(0.5f);
// Instantiate一个资源出来
GameObject tankInst = Instantiate(obj) as GameObject;
yield return new WaitForSeconds(0.5f);
// Destroy一个资源
GameObject.Destroy(tankInst);
yield return new WaitForSeconds(0.5f);
// Unload Resources
bundle.assetBundle.Unload(false);
yield return new WaitForSeconds(0.5f);
// 释放无用资源
//obj = null;
//Resources.UnloadUnusedAssets();
yield return new WaitForSeconds(0.5f);
}
执行结果如下:
统计结果如下:
| 数据描述 | Memory | Texture | Mesh | Material | GameObjects | Objects in Scene | Total Objects |
|---|---|---|---|---|---|---|---|
| 初始 | 59.9M | 1267/7.8M | 35/223.0K | 25/10.2K | 7 | 127 | 2099 |
| new WWW | 62.0M | 1267/7.8M | 35/223.0K | 25/10.2K | 7 | 127 | 2099 |
| AssetBundle.Load | 64.5M | 1268/9.2M | 36/0.8M | 26/10.5K | 7 | 127 | 2196 |
| Instantiate | 65.6M | 1268/9.2M | 36/0.8M | 26/10.7K | 52 | 219 | 2288 |
| Destroy | 63.9M | 1268/9.2M | 36/0.8M | 26/10.7K | 7 | 127 | 2196 |
| AssetBundle.Unload | 63.7M | 1268/9.2M | 36/0.8M | 26/10.7K | 7 | 127 | 2196 |
| Resources.UnloadUnusedAssets | 61.8M | 1267/7.8M | 35/223.0K | 25/10.2K | 7 | 127 | 2099 |
得出如下结论:
通过WWW Load AssetBundle的方式加载一个资源时会自动加载相应的Mesh,Texture和Material,而通过Resouces.Load方式进行加载只会加载Mesh信息。因此通过AssetBundle方式加载后Instantiate一个资源的内存消耗较小,本例中AssetBundle.Load增加了2.5M的内存,而Instantiate增加了1.1M的内存。相比较Resources.Load后Instantiate的内存增量要小很多。
3. 通过静态绑定的方法来Instantiate一个资源
代码如下:
IEnumerator InstResources()
{
Resources.UnloadUnusedAssets();
yield return new WaitForSeconds(5.0f);
GameObject inst = GameObject.Instantiate(tank, Vector3.zero, Quaternion.identity) as GameObject;
yield return new WaitForSeconds(1f);
GameObject.Destroy(inst);
yield return new WaitForSeconds(1f);
//释放无用资源
tank = null;
Resources.UnloadUnusedAssets();
yield return new WaitForSeconds(1f);
}
执行结果如下:
统计结果如下:
| 数据描述 | Memory | Texture | Mesh | Material | GameObjects | Objects in Scene | Total Objects |
|---|---|---|---|---|---|---|---|
| 初始 | 62.0M | 1268/7.9M | 36/0.8M | 25/10.2K | 7 | 134 | 2202 |
| Instantiate | 64.4M | 1269/9.2M | 36/0.8M | 26/10.7K | 8 | 137 | 2207 |
| Destroy | 64.0M | 1269/9.2M | 36/0.8M | 26/10.7K | 7 | 134 | 2204 |
| UnloadUnused Resources | 62.3M | 1268/7.9M | 35/226.3K | 25/10.2K | 7 | 134 | 2107 |
得出结论如下:
通过静态绑定的方式各种资源的加载顺序和Resources.Load的方式是一样的,一个GameObject创建时,其Component中静态绑定的GameObject只会加载Mesh信息,只有当该GameObject Instantiate出来之后才会加载Texture和Material信息。
理论篇
加载资源的过程可以分为两个阶段,第一阶段是使用Resources.Load或者AssetBundle.Load加载各种资源,第二阶段是使用GameObject.Instantiate克隆出一个新的GameObject。
Load的资源类型包括GameObject, Transform, Mesh, Texture, Material, Shader和Script等各种资源,但是Resources.Load和AssetBundle.Load是有区别的。
使用Resources.Load的时候在第一次Instantiate之前,相应的Asset对象还没有被创建,直到第一次Instantiate时才会真正去读取文件创建这些Assets。它的目的是实现一种OnDemand的使用方式,到该资源真正使用时才会去创建这些资源。
而使用AssetBundle.Load方法时,会直接将资源文件读取出来创建这些Assets,因此第一次Instantiate的代价会相对较小。
上述区别可以帮助我们解释为什么发射第一发子弹时有明显的卡顿现象的出现。
然后我们再来了解一下Instantiate的过程。Instantiate的过程是一个对Assets进行Clone(复制)和引用相结合的过程,Clone的过程需要申请内存存放自己的数据,而引用的过程只需要直接一个简单的指针指向一个已经Load的资源即可。例如Transform是通过Clone出来的,Texture和TerrainData是通过引用复制的,而Mesh,Material,PhysicalMaterial和Script是Clone和引用同时存在的。以Script为例,Script分为代码段和数据段,所有需要使用该Script的GameObject使用的代码是一样的,而大家的数据有所区别,因此对数据段需要使用Clone的方式,而对代码段需要使用引用的方式来复制。
因此Load操作其实Load一些数据源出来,用于创建新对象时被Clone或者被引用。
然后是销毁资源的过程。当Destory一个GameObject或者其他实例时,只是释放实例中那些Clone出来的Assets,而并不会释放那些引用的Assets,因为Destroy不知道是否有其他人在引用这些Assets。等到场景中没有任何物体引用到这些Assets之后,它们就会成为UnusedAssets,此时可以通过Resources.UnloadUnusedAssets来进行释放。AssetBundle.Unload(false)不行,因为它只会释放文件的内存镜像,不会释放资源;AssetBunde.Unload(true)也不行,因为它是暴力的释放,可能有其他对象在引用其中的Assets,暴力释放可能导致程序错误。
另外需要注意,系统在加载新场景时,所有的内存对象都会被自动销毁,这包括了Resources.Load加载的Assets, 静态绑定的Assets,AssetBundle.Load加载的资源和Instantiate实例化的对象。但是AssetBundle.Load本身的文件内存镜像(用于创建各种Asset)不会被自动销毁,这个必须使用AssetBundle.Unload(false)来进行主动销毁。推荐的做法是在加载完资源后立马调用AssetBunble.Unload(false)销毁文件内存镜像。
下图可以帮助理解内存中的Asset和GameObject的关系。
总结篇
- 为了不出现首次Instantiate时卡顿的现象,推荐使用AssetBundle.Load的方式代替Resources.Load的方式来加载资源;
- 加载完资源后立马调用AssetBunble.Unload(false)释放文件内存镜像;
- Unity自身没有提供良好的内存申请和释放管理机制,Destroy一个GameObject会马上释放内存而不是进行内部的缓存,因此应用程序对频繁不用的对象如NPC,FX等进行对象池管理是必要的,减少内存申请次数;
- 何时进行Resources.UnloadUnusedAssets是需要讨论的一个问题。
Ref:
http://game.ceeger.com/forum/read.php?tid=4394
http://game.ceeger.com/forum/read.php?tid=4466
Unity内存申请和释放的更多相关文章
- glibc 内存申请和释放及堆连续检查
C语言有两种内存申请方式: 1.静态申请:当你声明全局或静态变量的时候,会用到静态申请内存.静态申请的内存有固定的空间大小.空间只在程序开始的时候申请一次,并且不再释放(除非程序结束). 2.自动申请 ...
- C语言学习之我见-malloc和free内存申请及释放函数
malloc函数负责向计算机申请确定大小的内存空间. free函数负责释放malloc的申请空间. (1)函数原型 void free(void *_Memory); void * malloc(si ...
- C语言动态内存的申请和释放
什么是动态内存的申请和释放? 当程序运行到需要一个动态分配的变量时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量.当不再使用该变量时,也就是它的生命结束时,要显式释放它所占用的存储空 ...
- [CareerCup] 13.9 Aligned Malloc and Free Function 写一对申请和释放内存函数
13.9 Write an aligned malloc and free function that supports allocating memory such that the memory ...
- Win3内存管理之私有内存跟共享内存的申请与释放
Win3内存管理之私有内存跟共享内存的申请与释放 一丶内存简介私有内存申请 通过上一篇文章.我们理解了虚拟内存与物理内存的区别. 那么我们有API事专门申请虚拟内存与物理内存的. 有私有内存跟共享内存 ...
- C++函数中,两个自动释放内存的动态内存申请类
最近做一个事情,实现一个流程交互,其中主交互流程函数中,涉及较多的内存申请, 而健康的函数,都是在函数退出前将手动申请不再需要的内存释放掉, 使用很多方法,都避免不了较多的出错分支时,一堆的if fr ...
- Unity内存理解(转)
Unity3D 里有两种动态加载机制:一个是Resources.Load,另外一个通过AssetBundle,其实两者区别不大. Resources.Load就是从一个缺省打进程序包里的AssetBu ...
- unity内存管理(转)
转自:https://www.cnblogs.com/zsb517/p/5724908.html Unity3D 里有两种动态加载机制:一个是Resources.Load,另外一个通过AssetBun ...
- C语言中的内存分配与释放
C语言中的内存分配与释放 对C语言一直都是抱着学习的态度,很多都不懂,今天突然被问道C语言的内存分配问题,说了一些自己知道的,但感觉回答的并不完善,所以才有这篇笔记,总结一下C语言中内存分配的主要内容 ...
随机推荐
- 百度云管家 5.3.6 VIP破解不限速版下载分享|百度云管家破解提速
百度云管家PC客户端v5.3.6绿色版本,属于VIP破解不限速版.百度网盘为您提供文件的网络备份.同步和分享服务.空间大.速度快.安全稳固,支持教育网加速,支持手机端.它支持便捷地查看.上传.下载云端 ...
- VGA 视频输出
VGA Video Output by Nathan Ickes Introduction VGA is a high-resolution video standard used mostly fo ...
- <摘录>如何在64位linux强制编译32位应用程序
GDC注:因为需要解决在linux64机上编译32位的mongodb(没办法,因为编译的php是32位,然后我想将mongdb扩展添加到php中),在网上搜了很多文章,感觉这篇好懂,而且好用.我使用的 ...
- Python特殊语法:filter、map、reduce、lambda [转]
Python特殊语法:filter.map.reduce.lambda [转] python内置了一些非常有趣但非常有用的函数,充分体现了Python的语言魅力! filter(function, s ...
- MSSQL数据库链接字符串Asynchronous Processing=true不是异步查询吗,怎么是缓存
;Asynchronous Processing=true 不是异步查询吗,怎么是缓存 <!--<add name="default" providerName=&q ...
- 在CentOS上搭建apache和PHP服务器环境(转)
1.您也可以使用一键自动部署环境的工具,请参见网友开发的这个工具 http://www.centos.bz/2013/08/ezhttp-tutorial/ 2. 安装: wget -c http:/ ...
- 由Collections.unmodifiableList引发的重构
原文 http://www.cnblogs.com/persist-confident/p/4516741.html 今天阅读源码的时候,无意中看到了Collections.unmodifiable ...
- Eclipse 安装插件后不显示的解决办法
有时候一些 eclipse 插件安装之后,打开 eclipse 死活都不显示,这时候可以: ① 把 eclipse/configuration/org.eclipse.update 删除掉.出现这种情 ...
- [Angularjs]ng-include 包含
写在前面 有时我们需要动态的创建一些标签,或者在收到服务端返回的json,创建一些标签然后找到页面上的元素,通过innerHTML写入到页面上面.angularjs也为我们提供了一种比较方便操作方式, ...
- Javabean+servlet+JSP(html)实例应用
大家都知道Javabean+servlet+JSP是最简单的MVC模式.的确,在一个小型的项目中,这个模式完全够用. 它优雅并且简洁.加上jQueryui的完美展示效果,让这个模式看起来非常合适.当然 ...