转自: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内存申请和释放的更多相关文章

  1. glibc 内存申请和释放及堆连续检查

    C语言有两种内存申请方式: 1.静态申请:当你声明全局或静态变量的时候,会用到静态申请内存.静态申请的内存有固定的空间大小.空间只在程序开始的时候申请一次,并且不再释放(除非程序结束). 2.自动申请 ...

  2. C语言学习之我见-malloc和free内存申请及释放函数

    malloc函数负责向计算机申请确定大小的内存空间. free函数负责释放malloc的申请空间. (1)函数原型 void free(void *_Memory); void * malloc(si ...

  3. C语言动态内存的申请和释放

    什么是动态内存的申请和释放? 当程序运行到需要一个动态分配的变量时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量.当不再使用该变量时,也就是它的生命结束时,要显式释放它所占用的存储空 ...

  4. [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 ...

  5. Win3内存管理之私有内存跟共享内存的申请与释放

    Win3内存管理之私有内存跟共享内存的申请与释放 一丶内存简介私有内存申请 通过上一篇文章.我们理解了虚拟内存与物理内存的区别. 那么我们有API事专门申请虚拟内存与物理内存的. 有私有内存跟共享内存 ...

  6. C++函数中,两个自动释放内存的动态内存申请类

    最近做一个事情,实现一个流程交互,其中主交互流程函数中,涉及较多的内存申请, 而健康的函数,都是在函数退出前将手动申请不再需要的内存释放掉, 使用很多方法,都避免不了较多的出错分支时,一堆的if fr ...

  7. Unity内存理解(转)

    Unity3D 里有两种动态加载机制:一个是Resources.Load,另外一个通过AssetBundle,其实两者区别不大. Resources.Load就是从一个缺省打进程序包里的AssetBu ...

  8. unity内存管理(转)

    转自:https://www.cnblogs.com/zsb517/p/5724908.html Unity3D 里有两种动态加载机制:一个是Resources.Load,另外一个通过AssetBun ...

  9. C语言中的内存分配与释放

    C语言中的内存分配与释放 对C语言一直都是抱着学习的态度,很多都不懂,今天突然被问道C语言的内存分配问题,说了一些自己知道的,但感觉回答的并不完善,所以才有这篇笔记,总结一下C语言中内存分配的主要内容 ...

随机推荐

  1. CURL函数的GET和POST方式的两种写法(实现ajax跨域调用)

    POST请求 function curl_post($url='',$postdata='',$options=array()){ $ch=curl_init($url); curl_setopt($ ...

  2. linux命令:mkdir 命令详解

    linux mkdir 命令用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录. 1.命令格式: mkdir [选项] 目录... 2.命令 ...

  3. Azure媒体服务 直播延迟的原因解析

    当我们使用媒体服务的直播功能,会发现有时候会有较大的延迟,而延迟的产生和客户端以及推送软件的配置也有关系,本文以Wirecast为例进行分析 Encoder导致的延迟:在编码这一步骤的时候,它会消耗机 ...

  4. SPL--Serializable

    Serializable[自定义序列化的接口] : 实现此接口的类将不再支持 __sleep() 和 __wakeup(). 作用: 为一些高级的序列化场景提供支持.__sleep()和__wakeu ...

  5. <meta>标签元素的属性理解

    meta是用来在HTML文档中模拟HTTP协议的响应头报文.meta 标签用于网页的<head>与</head>中,meta 标签的用处很多.meta 的属性有两种:name和 ...

  6. HTML5 LocalStorage 本地存储,刷新值还在

    H5的两种存储技术的最大区别就是生命周期. 1. localStorage是本地存储,存储期限不限: 2. sessionStorage会话存储,页面关闭数据就会丢失. 使用方法: localStor ...

  7. Spring系列之基本配置

    一.概述Spring是一个轻量级的Java开源框架,是为了简化企业级系统开发而诞生的.Spring的核心是控制反转(IOC)和面向切面编程(AOP).主要有以下几个特点:(1)轻量:从大小和开销两方面 ...

  8. [MongoDB]增删改查

    摘要 上篇文章学习了mongodb在windows上的安装,以及如何开启mongodb,最后列举了简单的增删改查操作.本篇将继续深入学习一下增删改查. 相关文章 [MongoDB]入门操作 CRUD ...

  9. oracle vm virtualbox右ctrl切换显示模式

    转自: http://blog.csdn.net/lyc_daniel/article/details/44195515 virtualbox里面有个HOME键,注意这个HOME键不一定是键盘上的HO ...

  10. 清北学堂模拟day4 捡金币

    [问题描述]小空正在玩一个叫做捡金币的游戏.游戏在一个被划分成 n行 n列的网格状场地中进行.每一个格子中都放着若干金币,并且金币的数量会随着时间而不断变化. 小空的任务就是在网格中移动,拾取尽量多的 ...