UPL2-6——Memory Profiler Package

Memory Profiler Package

Memory Profiler Package(内存分析器包),是 Unity 提供给我们的一个关于内存分析的独立工具
它可以用于检查 Unity 应用程序以及 Unity 编辑器的内存使用情况
我们可以使用它来捕获、检查、比较内存快照!它获取的是当前时刻,全局的内存情况

它和我们上节课学习的 Memory 模块都是用于分析内存的,但是他们的定位有所不同,功能深浅有所不同

对比项 Profiler - Memory 模块 Memory Profiler(独立工具)
实时监控 支持 主要用于快照分析
适用阶段 开发中持续观察性能 排查具体内存泄漏 / 激增问题
查看细节 粗略数据(贴图、网格、GC 内存等) 精细到对象引用关系、类型大小、堆分布等
是否支持快照对比 不支持 支持多个快照对比
是否能定位内存泄漏 部分可以推测 具备定位能力
是否支持深入引用链分析
(即通过一层层追踪对象是如何被其他对象引用的)
不支持 支持
性能开销 极小 略高(抓取快照时会卡顿)

从对比中可以看出 Memory Profiler Package 具备更细致的内存问题定位能力,可以帮助我们更加细致的排查问题

总而言之:Memory Profiler Package 是一个比起 Profiler Memory 模块更加细致排查内存问题的模块

Memory Profiler Package 对于我们的意义

它可以通过捕获不同时间点的内存快照并进行对比,来定位内存泄漏、资源未释放、异常增长等问题

比如当我们进行某些操作时,前后各生成两次快照,来分析内存问题:

  1. 打开一个 UI 面板前后
  2. 运行 10 分钟前后
  3. 切换场景前后

等等

当我们想要:

  1. 想要详细对比两次运行之间的内存差异
  2. 想要知道到底是什么对象一直不释放
  3. 想要知道哪段代码导致了内存暴涨

等等,就可以可以使用它

总而言之:
想要更加深度的定位内存问题,我们可以使用它来进行排查,但是一般我们会配合上节课学习的 Memory 模块一起进行排查
通过 Profiler 中的 Memory 大概定位问题,再通过 Memory Profiler 来进行详细排查,Profiler 是望远镜,Memory Profiler 是显微镜

安装 Memory Profiler Package

在 Package Manager 内即可安装,除此以外,还可以直接在 Profiler 内安装:

image

安装 Memory Profiler Package 后,下方的 Simple 视图显示内容也会改变,变成 Summary 相关内容,点击 Open Memory Profiler 即可打开相应界面

image

初始状态 Memory Profiler Package 窗口

image

  • A:顶部菜单

    image

    1. 开/关左侧 快照 相关窗口
    2. 加载保存的内存快照
    3. 捕获内存快照(展开后可以选择要捕获的信息)
    4. 附加到应用程序(展开后可以选择要附加到的应用程序)
    5. 关闭右侧信息窗口
    6. 更多设置
  • B:当前选中内存快照

    其中,Single Snapshot 只选择一个快照,而 Compare Snapshots 可选择两个快照用于对比

    image

  • C:内存快照列表,每获取一次快照,就会在此处列表中添加一个快照信息

    image

  • D:快照具体信息,在没有选择快照时,中间会出现一个 Capture New Snapshot(捕获新快照)按钮,点击后便会捕获快照

    当捕获内存快照并选中一个快照后,会显示如下内容,具体内容在下文阐述

    image

    • Summary: 总结概要页签

      该页签主要显示快照中的内存总体信息,适合做快速分析和横向比较,常作为入口使用; 用于定位大块内存占用;对比多个快照

    • Unity Objects: Unity 对象页签

      展示所有 Unity 引擎级别的对象(非 C# 脚本对象),比如 Texture2D​、Mesh​、Material​、GameObject​、Component​、AnimationClip
      用于追查资源类内存开销来源、是否重复加载、是否未释放

    • All Of Memory: 所有内存页签

      它是这个工具中最强大、最底层、最全面的页签,显示包括托管堆(Managed Heap)在内的所有内存块
      用于追查复杂内存泄漏、查找引用链闭环、深入调试

Summary(总结概要页签)

该页签主要显示快照中的内存总体信息,该页签主要由以下四部分组成:

  1. Memory Usage On Device(设备上的内存使用情况)

    判断应用是否接近或超出设备内存上限

  2. Allocated Memory Distribution(已分配内存分布)

    快速识别主要内存消耗源头

  3. Managed Heap Utilization(托管堆使用率)

    用于分析 C# 脚本对象的内存使用效率

  4. Top Unity Objects Categories(Unity对象内存占用最多的类型分类)

    针对某类型内容优化的数据依据之一

image

其中,Allocated Memory Distribution,Managed Heap Utilization,和 Top Unity Objects Categories 部分右上角都有 Inspect 按钮,
Inspect 按钮可以跳转到更详细的分类页面,并自动筛选出该类内存的相关对象或资源,方便你进一步深入分析
它是定位问题的快速入口,适合先看 Summary 总览,再 drill-down(向下钻取) 到具体对象。
它不会改变快照本身,只是切换到对应视图并加好过滤
如果你正在查某个大类的内存异常问题(比如贴图暴涨、Mesh 堆积),使用 Inspect 可以极大提高定位效率

着重关注以下内容:

  1. 托管堆内存:是否有 GC 压力,是否有内存碎片化问题
  2. 原生内存:是否存在 Unity 原生对象内存泄漏,比如 GameObject​,Component、资源等
  3. 图形内存:是否存在贴图、渲染贴图显存异常
  4. 虚拟机内存:查看增长趋势
  5. 未追踪内存:是否存在大块内存异常,利用对应平台工具进行内存问题排查
  6. 预留内存:申请但没使用内存,是否存在内存碎片等情况

等等

我们可以通过对比多张内存快照来排查问题

Memory Usage On Device(设备上的内存使用情况)

主要作用:
展示当前快照中,Unity 在设备上实际占用了多少内存
可以用于判断当前是否接近设备内存限制,特别是移动设备上是否存在内存溢出的风险

image

补充 —— 内存溢出:
在 iOS / Android 上,系统会给每个 App 分配一段最大内存上限(比如 512MB、1GB 等,依设备不同而异)
如果你的游戏在运行过程中内存分配超出了这个上限,那么系统会判断该 App 占用内存超标,会强行杀死进程,不给任何提示!

一些统计数据(不准确,以真机为主)

设备 分配最大内存上限
Android 低端机 ~350 – 450MB
Android 中端机 ~500 – 600MB
Android 高端机 ~700MB – 1.2GB
iPhone 6s ~350MB
iPhone 12+ ~1GB+
iPad Pro ~1.5GB+

注意:建议在真机上进行调试排查

点击内存占用比例条,在右侧的信息面板可以看到如下内容:

image

设备上的内存使用情况

显示您已分配给系统的内存量,以及设备上当前驻留的内存量。分配的内存可以高于设备上可用的最大内存,而不会造成问题。

已分配内存

分配内存是指由进程分配的所有内存,该进程有操作系统提交的资源。资源可能位于物理内存中(在这种情况下,它被称为驻留)或交换到辅助存储,例如磁盘上的页面文件。如果分配的内存区域在一段时间内未被使用或未被访问,操作系统可能会决定压缩或将其移动到磁盘。这允许您的应用程序在物理内存中只保留它立即需要的东西。不利的一面是,对辅助存储的访问速度要慢得多,可能会影响您的应用程序性能。

检查分配的内存使用情况,以改善应用程序的总体健康状况。

设备上驻留内存总量

当前位于设备物理内存(RAM)中的已分配内存部分。它是应用程序对目标设备要求有多高的指标。如果常驻内存使用量增加,您可能会面临页面错误、性能下降或从系统中驱逐的风险。

检查应用程序的驻留内存使用情况,以降低内存不足的最直接风险。

Allocated Memory Distribution(已分配内存分布)

主要作用:
显示分配的内存如何在内存区域中分布
将光标悬停在类别上,查看分配的内存中有多少当前驻留在设备上。
可用于分析各个系统的内存占用比例,判断到底是谁“吃”内存

image

它由以下几个部分组成

  1. Native(原生内存):C++ 层对象,如 GameObject、资源加载、系统插件等;如果这里过高,排查是否未及时释放对象
  2. Managed(托管内存):C# 代码里的类、数组、List 等托管对象,如果过高;排查是否存在内存泄漏、GC压力大等问题
  3. Executables & Mapped(可执行文件和映射内存):可执行代码和库;如果过高,说明项目模块太多,建议开启代码裁剪(Stripping Level)
  4. Graphics (Estimated)(图形相关内存(估算)):GPU 纹理、Mesh、Animation 等资源;如果过高,检查贴图大小/格式/是否读写等问题
  5. Untracked*(未追踪的内存):Unity 无法识别的内存,若很大;可能是图形驱动、插件、平台相关等,需用平台专用工具查看(如 Android Profiler、Xcode Instruments)

点击内存占用比例条的不同部分,在右侧的信息面板可以看到如下内容:

Native(原生内存)

image

原生内存

原生内存(Native memory),被以下对象使用:

  • 场景对象(如 GameObject 和它们的组件)
  • 资源(Assets)和管理器(Managers)
  • 原生分配(Native Allocations),包括 NativeArray 和其他原生容器(Native Containers)
  • 图形资源在 CPU 端的内存占用
  • 以及其他类型的原生对象

这个分类不包括 GPU 图形内存,图形内存会在单独的 “Graphics” 类别中显示。

你可以在 All Of Memory Breakdown(全部内存明细) 中进一步查看这些分类的详细信息。

注意:Summary(概览)视图和 All Of Memory(全部内存)视图中的数值可能不同,因为它们在归类方式上是不同的。不同的方式将项目分组在一起。

建议:如果你正在分析某个 Native 内存异常增长的情况,建议重点查看 “All Of Memory” 中的 “Objects and Allocations” 和 “Memory Map”

Managed(托管内存)

image

托管内存

包含所有虚拟机和托管堆(Managed Heap)的内存

托管堆(Managed Heap)包含了与托管对象(Managed Objects)相关的数据,以及为这些对象预留的空间。它由脚本的垃圾回收器(Scripting Garbage Collector)进行管理,因此任何不再与根对象存在引用链的托管对象都会被回收。

托管内存中的“已使用”部分包括了用于托管对象的内存,以及一些无法归还(释放)但当前未被使用的空闲空间。

这个分类中的“已预留(reserved)”内存可以在需要时快速重复利用,或者会在每进行 6 次 GC.Collect 垃圾回收清扫时被归还给系统。

Executables & Mapped(可执行文件和映射内存)

image

可执行文件和映射内存

由应用程序构建代码占用的内存,包括所有共享库(shared libraries)和程序集(assemblies),无论是托管(Managed)还是原生(Native)的。目前,这个数值在所有平台上的报告还不完全一致。

你可以通过以下方式减少这部分内存使用:使用更高级别的代码剥离(Code Stripping Level,如 IL2CPP 下的 Stripping Level 设置) 减少对不同模块和库的依赖

Graphics (Estimated)(图形相关内存(估算))

image

图形相关(估算)

指的是图形驱动和 GPU 为渲染你的应用程序而使用的估算内存。

这些信息是基于 Unity 内部对图形资源分配情况的追踪得出的,包括:RenderTexture、Texture、Mesh、Animation 以及其他通过 Unity 或脚本 API 分配的图形缓冲区(Graphics Buffers)你可以在 All Of Memory 标签页中进一步查看这些图形资源。

注意事项: 并不是所有图形对象的内存都会体现在这个分类中。例如:启用了 Read/Write 的图形资源,在 CPU 内存中需要保留一份副本,这会使它们的总内存使用量翻倍。(之后会教大家如何排查 Read/Write 造成的问题(#TODO#)) 可以通过 Unity Objects 标签页来查看 Unity 对象的总内存使用情况。 此外,并非所有此类资源的内存都实际存在于 GPU 中。 Memory Profiler 无法准确获取图形资源是否驻留在 GPU 内存中的信息,因此这里只能提供估算值。

建议: 如果你在分析图形内存暴涨(如纹理、RT 多开-创建过多的RenderTexture)的问题,这段内容非常关键。
一般屏幕后处理、相机截图、多视角渲染、水面反射、投影、UI特效都会生成 RenderTexture 渲染纹理

Untracked* (未标记(追踪)的内存)

image

未标记(追踪)的内存

Untracked Memory(未追踪内存) 这部分内存是 Memory Profiler 尚无法追踪或归类的内存,原因可能包括:

  1. 平台特有的内存使用机制
  2. Memory Profiler 的追踪能力尚未覆盖
  3. 某些潜在的 bug 或内存记录漏洞

这部分 Untracked 内存的计算方式如下:
Memory Profiler 会分析整个 Unity 进程中已分配和常驻(resident)的内存区域,然后从中减去 Unity 已知的托管内存和原生内存分配器所使用的区域,剩下的部分就是无法归类的“未追踪”内存。要进一步分析这些未追踪内存,你需要使用平台专用的底层 Profiler(例如 Xcode Instruments、Android Studio Profiler 等)。

补充说明: 在计算 Untracked 内存的 “Allocated Size(已分配大小)” 时, 我们还会从中减去 Graphics(Estimated)图形内存的大小,因为:我们知道图形设备会申请某些类型的内存区域,但无法精确追踪每个图形资源具体映射到哪个内存块,所以我们直接从图形设备相关区域中减去 Graphics(Estimated)的总值,若找不到图形区域,则从最大的几个内存区域中扣减这部分图形估算值

建议:

  1. 想分析图形内存的详细情况, 用 Allocated Memory 视图
  2. 想准确查看 Untracked 未追踪部分 占用了多少内存、是否常驻;用 Resident Memory(常驻内存) 或 Allocated and Resident(分配和常驻)模式
  3. 如果你遇到 Untracked 非常大,建议结合平台原生工具(如 Xcode Instruments 的 VM Tracker、Android 的 meminfo)来做进一步排查

Managed Heap Utilization(托管堆使用率)

主要作用:
显示 Unity 脚本虚拟机(Scripting VM)所管理的内存的详细分类
可以用于分析 C# 脚本对象的内存使用效率
可以结合 GC 分析是否有频繁回收/堆扩容等问题

image

其中包括:

  1. Empty Heap Space(空闲堆空间)

    可能是之前被对象使用过,或者在上一次堆扩展时预留的空间
    托管堆中空闲区域,可能是就对象被GC清理后的空位,堆扩容后的未使用空间

  2. Objects(托管对象占用内存)

    托管堆内存,正在被引用的托管对象,占用堆内存,GC 不会清除的内存

  3. Virtual Machine(虚拟机使用的内存)

    脚本运行时(Mono / IL2CPP)自身占用的非对象内存,比如类型元数据、静态字段、泛型支持等

在排查问题时,主要关注:

  1. 托管对象(Managed Objects)所占内存,

    对比多个快照,看是不是存在对象一直不被释放,此时可能存在内存泄漏。
    或者看看对象创建速度是否超过了 GC 回收速度。

  2. 空闲堆内存在 GC 触发后,是否有明显回收、收缩(即内存被释放出来)

  3. 虚拟机内存是否不断增大

    如果存在可能是元数据膨胀或静态字段泄漏,即静态字段持有大量对象未释放,特别注意单例

  4. 不要单独只看一个数字,要结合堆大小、使用率、类型分布、引用关系综合判断

比较观察:

  1. 对比 托管对象所占内存 和 堆内存

    对象很少但堆很大,说明堆膨胀,可能存在内存碎片

  2. Empty Heap Space(空闲堆空间)高,已 GC 但堆未收缩,可能是 GC 没触发或碎片多

  3. Virtual Machine 增长快 泛型类过多或静态字段持有大对象未释放

我们可以进行多次快照对比

点击内存占用比例条的不同部分,在右侧的信息面板可以看到如下内容:

Empty Heap Space(空闲堆空间)

image

空闲堆空间

这个快照版本尚未能准确标识出哪个托管堆区段(Managed Heap Section)是当前活动的(Active)。因此,系统假设虚拟地址值最高的那个连续托管堆区段是活动的,这个判断大多数情况下是正确的。图中的进度条显示的是该活动区段中未使用内存的数量。

当需要为新的托管对象(Managed Objects)分配内存时:

  1. 首先会检查当前活动的堆区段中是否有空闲空间,这是分配内存最快的方式(直接连续分配)
  2. 如果活动堆区段中没有足够的连续空闲空间,脚本虚拟机(Scripting VM)就需要扫描“空闲块列表”和其他堆区段来寻找可用空间,这一步会更慢一些。
  3. 如果还是找不到合适的位置,则会触发 垃圾回收(GC.Collection

而 GC 这一步分两种情况:

  • 如果启用了 增量式 GC(Incremental GC):

    在找不到空间时,会立刻分配一个新的堆区段(扩展堆)来存放新对象,与此同时,垃圾回收会异步地进行

  • 如果未启用增量式 GC:

    系统会先尝试做一次完整的 GC,如果回收后仍然没有足够空间,才会新分配堆区段。另外,这部分内存中也可能仍然包含一些已“被放弃”的对象,即:上一次 GC 后已无引用的对象,但尚未被实际清理,等待下次 GC 时才会被回收。

重点排查:

  1. 堆有没有用满
  2. 是不是频繁 GC 或膨胀
  3. 有没有“看起来未使用但没被释放”的对象

Objects(托管对象占用内存)

image

对象

用于托管对象(Managed Objects)的内存,即那些由 C# 脚本代码中定义的类型创建和使用的对象所占用的内存。这个数字仅包括仍然被引用或保持存活状态的对象的内存。尚未被回收器 (Garbage Collector) 收集的托管对象,可能位于 活动堆区段的空闲区域(Empty Active / Fragmented Heap Space) 中。

重点:
这里统计的仅是“活跃托管对象”的内存使用,不会包括已失去引用但尚未 GC 的部分
垃圾回收尚未处理的对象,虽然没有引用,但仍可能暂时占用堆空间(在空闲或碎片区域里)
因此,Managed Objects 的数值只是堆使用的一部分,并不能代表堆中的全部存在内容

Virtual Machine(虚拟机使用的内存)

image

虚拟机

虚拟机内存(Virtual Machine memory) 包括 Mono 或 IL2CPP 运行所需的数据,例如:

  1. 每一个被使用的托管类型(Managed Type)的 类型元数据(Type MetaData)
  2. 字段与函数的定义(虚函数表 vTable)
  3. 静态字段的数据
  4. 泛型类型所需的支持数据

当 Memory Profiler 捕获快照时,它会初始化在托管堆中发现的所有类型,并因此将这些类型的元数据“膨胀”成完整数据结构(inflate)。因此,在一次分析会话中,第一次和第二次快照之间虚拟机内存的增长,通常主要是由于这些类型被初始化导致的。

尚未初始化的类型是指:没有任何代码访问过它们的类型相关数据,因此它们的静态构造函数(无论是显式的还是隐式的)尚未被调用。

这种情况通常出现在数组或泛型集合类中 —— 虽然集合被创建并使用了,但其内部类型数据还没有被实际访问。

重点:
Memory Profiler 抓快照时,会“触发”类型初始化,让一些原本没被使用的静态类型元数据加载进来,
这会让第二次快照比第一次“看起来多了虚拟机内存”,其实是因为被初始化了,
如果你在分析快照差异,看到 VM 内存上涨,不一定是泄漏,可能只是因为多扫了一些静态字段或泛型类

Top Unity Objects Categories(Unity 对象内存占用最多的类型分类)

主要作用:
显示在当前快照中,是哪些 Unity 类型对象占用最多内存
是我们针对某类型内容优化的数据依据之一

image

  • Texture2D:静态纹理(材质贴图)所占内存空间
  • RenderTexture:渲染纹理(屏幕后处理、相机目标输出、屏幕截图、倒影、UI等等)所占内存空间
  • Shader:着色器所占内存,包含着色器变体
  • Mesh:模型网格数据
  • AnimationClip:动画切片数据
  • SceneVisibilityState:Unity编辑器用来记录“场景可见性“的数据结构,仅在编辑器中会看到
  • MonoManager:内部对象,非暴露类,Unity 引擎内部的脚本生命周期管理器
  • Others:其他,未归类到已知类型的原生对象,比如原生插件对象等等

这些具体信息,都可以前往 Unity Objects 页签查看详细信息

Unity Objects(Unity 对象页签)

快速定位 Unity 对象内存占用的类型和具体实例

  1. 查找内存占用最大的资源,判断这些资源是否可以压缩或者延迟加载等
  2. 查找重复加载的资源
  3. 查看运行时创建但是没有释放的资源
  4. 查看脚本对象是否滞留内存

常见的一些问题:

  1. 纹理贴图内存过高:可以检查下是否重复加载,未压缩,是否开启 Read / Write
  2. 相同资源是否多次加载:Name 相同,但是 InstanceID 不同的对象
  3. 动态资源未释放:脚本中没有及时释放材质、纹理等
  4. 多个组件持有大资源引用
  5. MonoBehaviour 组件长期驻留,是否存在脚本组件内存泄漏(占着引用没使用)

等等

界面上的功能参数

image

  • A:对所有 Unity 对象所占内存的组成进行详细分类分析

    image

    • Allocated Memory(已分配内存)

      显示 Unity 已向操作系统申请的内存总量(可能在虚拟内存或被操作系统回收),一般比 驻留内存 大一些
      主要作用: 用来分析对象本身理论占用的内存量,可以快速判断大内存对象

    • Resident Memory on Device(驻留的内存)

      实际驻留在设备物理内存中的部分,表示当前确实占据了内存,排除了后台运行或系统清理后的内容,反应对象现在是否还在内存中
      主要作用: 在设备内存紧张时判断哪些对象仍常驻,可用于作为优化运行时内存压力的重要依据

    • Allocated and Resident Memory on Device(分配和驻留的内存)

      综合视图,显示对象分配的总内存以及在设备内存中的驻留情况,通常是最全面的视角。可以直观看出分类了多少,但真的再用的有多少
      主要作用: 找出大但驻留少的资源(说明是临时性的,加载后没用),找出分配不大但全部常驻的资源(说明常用)

  • B:所选内容的详细信息(点击某个大类时显示内容)

    image

    • Managed Type:对象的 C# 类型名

    • Managed Assembly:该对象所属程序集

    • Native Type:Unity原生对象的类型名称

    • Children Count:该对象的子对象数量(它引用了多少其他对象)

      注意:不是场景层级的子物体,而是内存引用结构中的“下层持有对象”,比如一个材质球可能持有一个着色器和多个纹理

  • C:对象内存占用情况

    image

    • Description:对象简要说明,一般是对象名称
    • Allocated Size:该对象在 Unity 进程内部分配器中记录的总分配内存大小
    • Resident Size:该对象实际驻留在设备物理内存中的内存大小
    • %Impact:该对象占整体内存的百分比
    • Native Size:对象在 Unity 原生内存(C++层)中分配的大小,比如纹理、材质、网格这些大多都是原生对象
    • Managed Size:对象在托管内存(C#)中占用的大小, 比如 MonoBehaviour​、ScriptableObject、托管数组、类等对象
    • Graphics Size:对象在 GPU 图形内存(显存)中占用的估算大小, 比如纹理的像素,RenderTexture 的 RT 缓冲区,Mesh 的顶点/索引缓冲

    该表内的大类都可以展开查看每个类型下哪些资源占内存最多,并且每个子项可以点击,右侧会显示该资源的引用情况和,不同类型资源显示详细数据内容不同

    image

驻留内存(Resident Memory)

真正对设备内存造成压力的,其实是驻留内存(Resident Memory)
因为它才是实际驻留在物理内存(RAM)中的部分,也就是说系统此刻必须为它分配真实的内存

一旦超过物理内存上限,就容易造成:

  1. 系统强制垃圾回收,导致卡顿,掉帧
  2. 内存溢出(Out of Memory,简称 OOM),系统直接强行杀掉进程

总结:驻留内存决定了程序的生死

已分配内存(Allocated Memory)

数值上可以超过物理限制,不会导致问题

举例说明:
比如申请了 6GB 内存,但其中只有 2.5GB 驻留在 RAM 中,
就算操作系统没有 6GB 可用内存,依然会“允许”你申请成功(登记虚拟地址空间),它会允许你申请成功,
但是如果你真正想要驻留超过上限的内存,系统会毫不留情的干掉你

总结:申请不是问题,真正驻留、访问才决定生死

既然已分配内存(Allocated Memory)不决定生死,那为什么还要统计它?
原因:它虽不决定“立刻死亡”,但决定“健康状况和崩溃风险趋势”

统计它的主要目的:

  1. 提前预警 内存溢出 风险

    已分配内存很高(Allocated Memory)意味着迟早会驻留,OOM 是时间问题

  2. 衡量资源使用的总体趋势

    有些内存你还没用到或暂时没驻留,但它反映了程序在“计划”使用的资源规模,是设计是否合理的量化依据

  3. 识别内存泄漏

    如果 已分配内存(Allocated Memory)一直上涨却没有回落,极可能是资源没有释放或发生了内存泄漏(特别是原生资源或 C++ 插件)

  4. 优化内存分布结构

    不合理的内存使用分布(如 纹理相关 占一半,托管堆相关 占很少),可以通过观察 已分配内存(Allocated Memory) 各子类分布来发现

  5. 与平台限制进行对比

    虽然当前驻留量没爆,但你可能分配了超过平台上限的内存(比如 Android 中 4GB 上限),一旦压力加大就会踩雷

再次总结:

  • 驻留内存(Resident Memory):决定生死(是否内存溢出)
  • 已分配内存(Allocated Memory):决定体质(是否健康)

虽然最终关心的是 内存溢出 是否发生,但通过监控已分配内存(Allocated Memory)可以提前发现问题、指导设计和优化,从而防患于未然!

All Of Memory(所有内存页签)

它是这个工具中最强大、最底层、最全面的页签,它将内存分配按照分配方式进行了分类

我们着重要分析的内容有以下几点:

  1. 哪些模块分配了最多内存
  2. 哪些内存是托管的,哪些是原生的,哪些是图形的
  3. 那些内存增长但没有释放
  4. 是否出现了内存碎片、预留过多、未追踪内存暴涨等问题

着重应该关注的内存类别为:

  • 托管内存:排查托管堆占用内存是否过高,是否有 GC 问题
  • 资源所占内存:排查资源对象(纹理、网格、材质球)是否重复加载或者未释放
  • 图形内存:查看纹理、渲染纹理、网格的显存是否有异常,是否因为开启 Read / Write 有双份内存
  • 原生分配:排查 NativeArray​、UnsafeUtility.Malloc 是否有泄漏,特别是使用 DOTS 时
  • 未追踪内存:查看是否有无法追踪的大块内存,这往往是由于驱动、平台问题造成的潜在泄漏
  • 预留内存:分析内存碎片或预留过高的区域,确认是否系统内存无法被释放

界面上的功能参数

image

  • A:Unity 所能是被和追踪到的所有内存的详细分类分析

    image

    • Allocated Memory(已分配内存)

      显示 Unity 已向操作系统申请的内存总量(可能在虚拟内存或被操作系统回收),一般比 驻留内存 大一些
      主要作用: 用来分析对象本身理论占用的内存量,可以快速判断大内存对象

    • Resident Memory on Device(驻留的内存)

      实际驻留在设备物理内存中的部分,表示当前确实占据了内存,排除了后台运行或系统清理后的内容,反应对象现在是否还在内存中
      主要作用: 在设备内存紧张时判断哪些对象仍常驻,可用于作为优化运行时内存压力的重要依据

    • Allocated and Resident Memory on Device(分配和驻留的内存)

      综合视图,显示对象分配的总内存以及在设备内存中的驻留情况,通常是最全面的视角。可以直观看出分类了多少,但真的再用的有多少
      主要作用: 找出大但驻留少的资源(说明是临时性的,加载后没用),找出分配不大但全部常驻的资源(说明常用)

  • B:所选内容的详细信息

    • 当点击某个大类下具体的某种类型(托管类型)时,右侧信息面板显示内容如下:

      image

      • Managed Type:对象的 C# 类型名

      • Managed Assembly:该对象所属程序集

      • Native Type:Unity原生对象的类型名称

      • Children Count:该对象的子对象数量(它引用了多少其他对象)

        注意:不是场景层级的子物体,而是内存引用结构中的“下层持有对象”,比如一个材质球可能持有一个着色器和多个纹理

    • 当点击某个大类时,右侧信息面板显示会显示该大类代表的含义

      image

      它显示的内容和 Summary 页签中的 Allocated Memory Distribution 部分对各个内存类型的描述一致
      因此,可以直接参考 Allocated Memory Distribution(已分配内存分布)内对各个内存类型的描述,这里不再阐述

  • C:内存分配详细情况

    image

    • Description:描述;内存分类名或功能模块简要说明
    • Allocated Size:已分配内存大小
    • Resident Size:该对象实际驻留在设备物理内存中的内存大小
    • %Impact:这项占整个快照内存总量的百分比

All Of Memory(所有内存)Unity Objects(Unity 对象)页签区别

Unity Objects(Unity 对象页签) All Of Memory(所有内存页签)
关注角度 按 Unity 对象类型分类,如:Texture、RT、Shader 等 按内存类别或区域类型分类,如:Native、Managed、Graphics
分析角度 分析指定类型对象占用内存情况 分析 Unity 整体内存结构、分配和驻留情况
排查方向 找出哪个资源占用最多 分析整体内存健康情况
定位问题 资源暴涨、泄漏的原因等 内存碎片、未归类区域、驻留异常等
  • Unity Objects(Unity对象)页签是 面向对象 去分析内存使用情况

    更适合排查资源、对象等内存问题,它更像是一个资源仓库清单,列出了每类物品(资源)有多少,占用了多少内存

  • All Of Memory(所有内存)页签是 面向内存类型 去分析内存使用情况

    更适合排查系统内存问题,它更像是一个仓库布局图,列出了哪个区域占了多少,是否堆积,是否有空间被浪费