第三章 AssetBundle内存分析之——内存去哪了?

首先要介绍几个Profiler中的名词:

  • ManagedHeap.UsedSize (Mono环境下堆使用内存量)
  • ManagedHeap.ReservedUnusedSize (Mono环境下堆空闲内存量)

内存堆只会增加不会减小,当内存被释放后,内存并不会还给系统,内存首先会使用堆上空闲部分,当空闲部分不足时才会向系统申请新的空间,在移动平台上一个 app 如果使用的内存过大导致系统无法承受就会被系统强制结束,造成闪退的出现。

本章节将引用一个Unity工程进行说明,相关工程可自行下载:https://github.com/mister91jiao/AssetBundleMemory

第一步 加载AssetBundle

首先我们准备一个 AssetBundle,这里就直接使用演示工程的资源。

将三张贴图分别做成三个材质,并且创建三个 Cube 将材质赋予,然后将这些资源构建成一个 AssetBundle,命名为 testab.bundle,可以看到这个 AssetBundle 的大小是 1603kb 也就是 1.56mb。

将工程以调试模式打包,运行起来后,我们通过 Profiler 查看,发现其内存消耗如图所示。这时候开始进行第一步,将 AssetBundle 加载到内存。

然后再次对 profiler 进行观察。

通过对比我们发现,其中 Other 项里多了 2.9mb 的内存,但我们的资源明明 只有 1.56 mb,这是为什么呢?这个时候将 Other 项展开得到图

我们发现在 AssetBundle 下面 有一个 LoadingCache 的项占用了整整 1mb 的内存,但这个并不是我们的资源,这个是因为 Unity 需要有一个索引表去缓存每个 AssetBundle 内都有哪些资源,这样当再次加载其中的某一个资源的时候,可以加速查找,而这个缓存表的默认大小就是 1mb,会在第一次加载 AssetBundle 的时候生成,关于这部分可以参考Unity的官方手册:https://docs.unity3d.com/2021.2/Documentation/ScriptReference/AssetBundle-memoryBudgetKB.html

去掉这 1mb 的内存后,在 ManagedHeap.UsedSize 里就有我们加载的数据以及 AssetBundle 对象所占用的堆内存的大小,大约是 2mb 这与我们的资源量大小对应。

在 BundleMaster 中如果不使用异或加密并且压缩格式不是 LZMA ,则加载 AssetBundle 使用的 API 是 LoadFromFile,就不需要将数据缓存到堆上。但本次演示依然使用 LoadFromMemory 的方式进行 AssetBundle 的加载。

第二步 加载资源

我们将 AssetBundle 里所有的 Prefab 加载出来,因为他们每个 Prefab 都依赖了贴图,可以说相当于把整个 AssetBundle 里的内容加载出来,然后对 profiler 进行观察。

然后发现,Assets 部分有了明显增加,而其它部分并未改变,我们将 Assets 项展开。

当把 AssetBundle 里的资源,主要是那几张图片加载出来后,明显占用了内存,尤其是那个 zfnp,一个资源就占用了 9.6mb 的内存。

第三步 实例化

刚才提到,我们加载的是 Prefab,现在将 GameObject 全部示例化,并将实例化后的对象存在 _gameObjects 数组里。

然后再观察 profiler。

可以看到 Scene Memory 项有一点增加,这是因为当我们实例化对象的时候,对象身上的一些组件都会被创建出来,如果展开的化就会发现增加的这些内存是因为对象身上的组件造成的,可以说对象实例化的开销对内存很小。

第四步 销毁实例化的对象

将刚才实例化的对象进行销毁,并且重新查看 profiler。

可以看到内存占用回到了实例化之前的状态。

第五步 卸载AssetBundle

将 AssetBundle 卸载后,再查看profiler。

可以看到 Assets 项已经被卸载,回到了初始的样子,但 Other 项 似乎并没有发生什么变化,这是为什么呢?展开看看。

原来 AssetBundle 的 LoadingCache 并没有回收,因为只要加载过 AssetBundle 就会一直有这个,其中 ManagedHeap.UsedSize 确实减少了,但减少的部分跑到了 ManagedHeap.ReservedUnusedSize 上,当下次再加载的时候就不需要再向系统申请内存了,直接用 ManagedHeap.ReservedUnusedSize 里的内存空间,再重新加载一下 AssetBundle 验证一下。

可以看到 218.6mb 的占用和第一次加载 AssetBundle 的时候一样,而 ManagedHeap.ReservedUnusedSize 减少了,ManagedHeap.UsedSize 的占用增加了。

最后补充

从上面的测试流程来看,已经清楚的说明了 AssetBundle 内存占用的来龙去买,但这都是在 Mono 环境下运行的,在 IL2CPP 的情况下会不会有所不同呢?虽然 Unity 的 IL2CPP 是将代码转为 C++ 运行,但依然是托管内存,不过不可以直接在 Profiler 里看到 ManagedHeap.UsedSize 和 ManagedHeap.ReservedUnusedSize 这两个参数,如果想看到占用堆内存的情况,可以通过 pinvoke 的方式调用 il2cpp 的代码来获取数据。

感谢walon大佬的提示

91焦先生

2022/07/20

第三章 AssetBundle内存分析之——内存去哪了?》有1个想法

评论已关闭。