了解一下加载AB和实例化操作对应的内存变化,有助于我们更好的理解内存管理

很经典的对称造型,用多少释放多少。

这是各阶段的内存和其他数据变化




unity 手机 内存 unity3d内存_unity 手机 内存


说明:

  1. 初始状态
  2. AssetBundle.Load文件后 ==》 内存多了文件镜像,Memory+4.4MB,Total Object和Assets+1(AssetBundle也是object)
  3. 载入Texture后 ==》 Memory+4MB,因为多了Texture Asset占用的内存,Total Objects和Assets+1
  4. 载入Prefab后 ==》 内存无明显变化,因为最占内存的Texture已经加载,Materials+1是因为多了Prefab的材质,Total Objects和Assets+6,因为 Perfab 包含很多 Components
  5. 实例化Prefab以后 ==》 显存(Texture Memory+4MB、GameObject、Total Objects in Scene上升,都是因为实例化了一个可视的对象
  6. 下面是开始对称的操作
  7. 销毁实例后 ==》 Texture Memory-4MB,GameObject、Total Objects in Scene下降
  8. 卸载AssetBundle文件后 ==》 AssetBundle文件镜像占用的内存被释放Memory-4.3MB(??泄露了?,相应的Assets和Total Objects Count也减1
  9. 直接Resources.UnloadUnusedAssets ==》 没有任何变化,因为所有Assets引用并没有清空
  10. 把Prefab引用变量设为null以后,再Resources.UnloadUnusedAssets ==》 整个Prefab除了Texture外都没有任何引用了,所以被UnloadUnusedAssets销毁,Assets和Total Objects Count -6Materials-1
  11. 再把Texture的引用变量设为null,再Resources.UnloadUnusedAssets ==》 之后也被UnloadUnusedAssets销毁,内存被释放Texture Memory-4MBassets和Total Objects Count -1,基本还原到初始状态

综上:

  • AssetBundle.Load 内存++【多了文件镜像】
  • 实例化Prefab,显存++【多了可视对象】

Texture加载以后是到内存,显示的时候才进入显存的Texture Memory。 所有的东西基础都是Object Load的是Asset,Instantiate的是GameObject和Object in Scene Load的Asset要Unload,new的或者Instantiate的object可以Destroy

下面我们详细说一下细节概念:

一、ab加载与释放

  1. AssetBundle.CreateFromFile运行时加载:只是个AssetBundle内存镜像数据块
  2. AssetBundle.Load(同Resources.Load) 这才会从AssetBundle的内存镜像里读取并创建一个Asset对象,创建Asset对象同时也会分配相应内存用于存放(反序列化)
  3. AssetBundle的释放:
  • AssetBundle.Unload(flase)是释放AssetBundle文件的内存镜像,不包含Load创建的Asset内存对象。
  • AssetBundle.Unload(true)是释放那个AssetBundle文件内存镜像和并销毁所有用Load创建的Asset内存对象。

二、实例化

一个Prefab从assetBundle里Load出来,里面可能包括:Gameobject、transform、mesh、texture、material、shader、script和各种其他Assets

Instaniate一个Prefab,是一个对Assets进行Clone(复制)+引用结合的过程,GameObject、transform 是Clone是新生成的。

  • 其他mesh / texture / material / shader 等,这其中些是纯引用的关系的,包括:Texture和TerrainData,
  • 还有引用和复制同时存在的,包括:Mesh/material /PhysicMaterial。
  • 引用的Asset对象不会被复制,只是一个简单的指针指向已经Load的Asset对象。这种含糊的引用加克隆的混合, 大概是搞糊涂大多数人的主要原因。

总结一下各种释放

  • Destroy: 主要用于销毁克隆对象,也可以用于场景内的静态物体,不会自动释放该对象的所有引用。虽然也可以用于Asset,但是概念不一样要小心,如果用于销毁从文 件加载的Asset对象会销毁相应的资源文件!但是如果销毁的Asset是Copy的或者用脚本动态生成的,只会销毁内存对象。
  • AssetBundle.Unload(false):释放AssetBundle文件内存镜像
  • AssetBundle.Unload(true):释放AssetBundle文件内存镜像同时销毁所有已经Load的Assets内存对象
  • Reources.UnloadAsset(Object):显式的释放已加载的Asset对象,只能卸载磁盘文件加载的Asset对象
  • Resources.UnloadUnusedAssets():用于释放所有没有引用的Asset对象
  • GC.Collect():强制垃圾收集器立即释放内存 Unity的GC功能不算好,没把握的时候就强制调用一下

举两个例子帮助理解

例子1:

一个常见的错误:

  • 你从某个AssetBundle里Load了一个prefab并克隆之:obj = Instaniate(AssetBundle1.Load('MyPrefab”);
  • 这个prefab比如是个npc
  • 然后你不需要他的时候你用了:Destroy(obj);你以为就释放干净了
  • 其实这时候只是释放了Clone对象,通过Load加载的所有引用、非引用Assets对象全都静静静的躺在内存里。
  • 这种情况应该在Destroy以后用:AssetBundle1.Unload(true),彻底释放干净。
  • 如果这个AssetBundle1是要反复读取的 不方便Unload,那可以在Destroy以后用:Resources.UnloadUnusedAssets()把所有和这个npc有关的Asset都销毁。
  • 当然如果这个NPC也是要频繁创建 销毁的 那就应该让那些Assets呆在内存里以加速游戏体验。
  • 由此可以解释另一个之前有人提过的话题:为什么第一次Instaniate 一个Prefab的时候都会卡一下?

因为在你第一次Instaniate之前,相应的Asset对象还没有被创建,要加载系统内置的 AssetBundle并创建Assets。第一次以后你虽然Destroy了,但Prefab的Assets对象都还在内存里,所以就很快了。

例子2:

  • 从磁盘读取一个1.unity3d文件到内存并建立一个AssetBundle1对象
  • AssetBundle AssetBundle1 = AssetBundle.CreateFromFile("1.unity3d");
  • 从AssetBundle1里读取并创建一个Texture Asset,把obj1的主贴图指向它
  • obj1.renderer.material.mainTexture = AssetBundle1.Load("wall") as Texture;
  • 把obj2的主贴图也指向同一个Texture Asset
  • obj2.renderer.material.mainTexture =obj1.renderer.material.mainTexture;
  • Texture是引用对象,永远不会有自动复制的情况出现(除非你真需要,用代码自己实现copy),只会是创建和添加引用
  • 如果继续:
  • AssetBundle1.Unload(true) 那obj1和obj2都变成黑的了,因为指向的Texture Asset没了
  • 如果:
  • AssetBundle1.Unload(false) 那obj1和obj2不变,只是AssetBundle1的内存镜像释放了
  • 继续:
  • Destroy(obj1); //obj1被释放,但并不会释放刚才Load的Texture
  • 如果这时候:
  • Resources.UnloadUnusedAssets();
  • 不会有任何内存释放 因为Texture asset还被obj2用着
  • 如果
  • Destroy(obj2);
  • obj2被释放,但也不会释放刚才Load的Texture
  • 继续
  • Resources.UnloadUnusedAssets();
  • 这时候刚才load的Texture Asset释放了,因为没有任何引用了
  • 最后CG.Collect();
  • 强制立即释放内存

由此可以引申出论坛里另一个被提了几次的问题,如何加载一堆大图片轮流显示又不爆掉?

不考虑AssetBundle,直接用www读图片文件的话等于是直接创建了一个Texture Asset 假设文件保存在一个List里


TLlist<string> fileList;
int n=0;
IEnumerator OnClick()
{
    WWW image = new www(fileList[n++]);
    yield return image;
    obj.mainTexture = image.texture;
    n = (n>=fileList.Length-1)?0:n;
    Resources.UnloadUnusedAssets();
}


这样可以保证内存里始终只有一个巨型Texture Asset资源,也不用代码追踪上一个加载的Texture Asset,但是速度比较慢
或者:


IEnumerator OnClick()
{
    WWW image = new www(fileList[n++]);
    yield return image;
    Texture tex = obj.mainTexture;
    obj.mainTexture = image.texture;
    n = (n>=fileList.Length-1)?0:n;
    Resources.UnloadAsset(tex);
}


这样卸载比较快

参考:

Unity3D内存释放 (很详细的)