Unity Memory Management
Unity 的 Memory 构造
实际上Unity游戏使用的内存一共有三种:程序代码、托管堆(Managed Heap)以及本机堆(Native Heap)。
程序代码包括了所有的Unity引擎,使用的库,以及你所写的所有的游戏代码。在编译后,得到的运行文件将会被加载到设备中执行,并占用一定内存。 这部分内存实际上是没有办法去“管理”的,它们将在内存中从一开始到最后一直存在。一个空的Unity默认场景,什么代码都不放,在iOS设备上占用内存应该在17MB左右,而加上一些自己的代码很容易就飙到20MB左右。想要减少这部分内存的使用,能做的就是减少使用的库,稍后再说。
托管堆是被Mono使用的一部分内存。Mono项目一个开源的.net框架的一种实现,对于Unity开发,其实充当了基本类库的角色。 托管堆用来存放类的实例(比如用new生成的列表,实例中的各种声明的变量等)。“托管”的意思是Mono“应该”自动地改变堆的大小来适应你所需要的内存, 并且定时地使用垃圾回收(Garbage Collect)来释放已经不需要的内存。关键在于,有时候你会忘记清除对已经不需要再使用的内存的引用, 从而导致Mono认为这块内存一直有用,而无法回收。
最后,本机堆是Unity引擎进行申请和操作的地方,比如贴图,音效,关卡数据等。Unity使用了自己的一套内存管理机制来使这块内存具有和托管堆类似的功能。基本理念是,如果在这个关卡里需要某个资源,那么在需要时就加载,之后在没有任何引用时进行卸载。听起来很美好也和托管堆一样,但是由于Unity有一套自动加载和卸载资源的机制,让两者变得差别很大。自动加载资源可以为开发者省不少事儿,但是同时也意味着开发者失去了手动管理所有加载资源的权力,这非常容易导致大量的内存占用(贴图什么的你懂的),也是Unity给人留下“吃内存”印象的罪魁祸首。
iOS game profile
Unity has already created a basic guide on using Instruments to profile iOS games. It can be found here.
https://blogs.unity3d.com/cn/2016/02/01/profiling-with-instruments/
Unity 提供的 Profiler
进入Detialed,单击Take Sample: Editor可以看到以下几类内存数据
Assets: Asset referenced from user or native code (含有code里创建的game object)
Built-in Resources: Unity Editor resources or Unity default resources
Not Saved: GameObjects marked as DontSave
Scene Memory: GameObject and attached components (在当前scene里创建的game object)
When loading a new level all objects in the scene are destroyed, then the objects in the new level are loaded.
In order to preserve an object during level loading call
DontDestroyOnLoad
on it. If the object is a component or game object then its entire transform hierarchy will not be destroyed either.Other: GameObjects not marked in the above categories 项目中通过代码生成的各种资源记录
Not Saved
Ref: http://blog.csdn.net/quan2008happy/article/details/39352535
DontSave:保留对象到新场景
功能说明:此属性的功能是用来设置是否将Object对象保留到新的场景(Scene)中,如果使用HideFlags.DontSave,则Object对象将在新场景中被保留下来,对其使用说明如下。
(1)如果GameObject对象被HideFlags.DontSave标识,则在新scene中GameObject的所有组件将被保留下来,但其子类GameObject对象不会被保留到新scene中。
(2)不可以对GameObject对象的某个组件如Transform进行HideFlags.DontSave标识,否则无效。
(3)即使程序已经退出,被HideFlags.DontSave标识的对象会一直存在于程序中,造成内存泄漏,对HideFlags.DontSave标识的对象在不需要或程序退出时需要使用DestroyImmediate手动销毁。
官方文档 https://docs.unity3d.com/ScriptReference/HideFlags.DontSave.html
The object will not be saved to the scene. It will not be destroyed when a new scene is loaded. It is a shortcut for HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor | HideFlags.DontUnloadUnusedAsset.
It is your responsibility to cleanup the object manually using DestroyImmediate, otherwise it will leak.
Other
-
上面所提到的
托管堆
相关- ManagedHeap.UsedSize:托管堆使用大小(表示上次GC到目前为止所分配的堆内存量)。移动游戏建议不超过20M。
- ManagedHeap.ReservedUnusedSize:托管堆预留不使用内存大小,只由Mono使用。
-
我们值得关注的东西
-
SerializedFile: AssetBundle加载时产生的序列化信息,一般为LoadFromCacheOrDownload、LoadFromFile和New WWW加载本地AssetBundle文件所致,SerializedFile中记录的是AssetBundle的序列化信息,而不是其包含资源的内容,因此,其大小要小于或远小于资源的实际内存[^Zhihu]。实测SerializedFile所占的内存远小于bundle的实际内存,但是目前还是很容易上10M的。
- 经过在Editor下实测,发现包括加载AssetBundle时产生的序列化信息,Editor导入资源时产生的Metadata的序列化信息,以及名为
library/unity default resources
和resouces.asssets
的两个文件。AssetBundle相关可以通过调用Unload(false)释放AssetBundle,从而释放对应的SerializedFile。Metadata相关在切换UI后会自动出现/消失,怀疑是在预加载UI这一类的。library/unity default resources
也会不时消失,resouces.asssets
似乎一直都在。不过值得欣慰的是这些不时出现的SerializedFile每个都在20K以内,而比较大的AssetBundle时产生的SerializedFile我们可以解决。 - 在windows下实测,unload所有AssetBunles之后,SerializedFile这部分变为几K。
- 序列化是啥?[1]
- serialize = save
- deserialize = load
- Unity官方文档[2]的解释,大意就是说load asset的时候要从SerializedFile中deserialize信息。
Asset loading can also be identified in CPU traces. The main method indicating an Asset load is
SerializedFile::ReadObject
. This method connects a binary data stream (from a file) to Unity’s serialization system, which operates via a method namedTransfer
. TheTransfer
method can be found on all Asset types, such as Textures, MonoBehaviours and Particle Systems.This (scene loading) requires Unity to read and deserialize all the Assets within the Scene, as denoted by the calls to various
Transfer
methods beneathSerializedFile::ReadObject
. - 经过在Editor下实测,发现包括加载AssetBundle时产生的序列化信息,Editor导入资源时产生的Metadata的序列化信息,以及名为
-
SerializedFile: AssetBundle加载时产生的序列化信息,一般为LoadFromCacheOrDownload、LoadFromFile和New WWW加载本地AssetBundle文件所致,SerializedFile中记录的是AssetBundle的序列化信息,而不是其包含资源的内容,因此,其大小要小于或远小于资源的实际内存[^Zhihu]。实测SerializedFile所占的内存远小于bundle的实际内存,但是目前还是很容易上10M的。
-
一些我们并没什么办法的东西
System.ExecutableAndDlls: 系统可执行程序和DLL,是只读的内存,用来执行所有的脚本和DLL引用。(存疑:不同平台和不同硬件得到的值会不一样,可以通过修改Player Setting的Stripping Level来调节大小。)
GfxClientDevice: GFX(图形加速\图形加速器\显卡 (GraphicsForce Express))客户端设备。
ShaderLab: Unity自带的着色器语言工具相关资源。
PersistentManager.Remapper: 持久化数据重映射管理相关
与持久化数据相关,比如AssetBundle之类的。注意监控相关的文件。(存疑!实验load assetbundle并不会导致这部分内存增加。不过这部分一般只占18KB左右。
Assets
GameObject: code里创建的game object
sprite
Scene Memory
如果load一些asset再回到原来的UI,这部分基本没有变化。因为它存储的是当前的场景中各个方面的内存占用情况。
包括GameObject、所用资源、各种组件以及GameManager等(一般情况通过AssetBundle加载的不会显示在这里)
GameObject and attached components
Unity Memory API
-
Script Serialization: https://docs.unity3d.com/Manual/script-Serialization.html ↩
-
SerializedFile: https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity1.html ↩