10.MemoryProfiler窗口 Summary

10.Unity性能分析工具-MemoryProfiler窗口Summary


10.1 知识点

Summary(总结摘要)的主要内容

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

  1. Memory Usage On Device(设备上的内存使用情况)
  2. Allocated Memory Distribution(已分配内存分布)
  3. Managed Heap Utilization(托管堆使用率)
  4. Top Unity Objects Categories(Unity 对象内存占用最多的类型分类)

Inspect 按钮

  • 跳转到更详细的分类页面,并自动筛选出该类内存的相关对象或资源,方便进一步深入分析
  • 适合先看 Summary 总览,再 drill-down(向下钻取)到具体对象
  • 不会改变快照本身,只是切换到对应视图并加好过滤条件
  • 如果在查某个大类的内存异常(比如贴图暴涨、Mesh 堆积),使用 Inspect 可以明显提高定位效率

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

主要作用

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

补充:内存溢出(OOM)

在 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+

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


下面这段是面板提示信息(原文):

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

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

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

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

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

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

主要作用

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

它由以下几个部分组成:

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

详细解析

Native(原生内存)

原生内存(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 AllocationsMemory Map

Managed(托管内存)

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

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

托管内存中的“已使用”部分包括:

  • 用于托管对象的内存
  • 一些无法归还(释放)但当前未被使用的空闲空间

这个分类中的“已预留(reserved)”内存:

  • 可以在需要时快速重复利用
  • 或者会在每进行 6 次 GC.Collect 垃圾回收清扫时被归还给系统

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

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

你可以通过以下方式减少这部分内存使用:

  • 使用更高级别的代码剥离(Code Stripping Level,如 IL2CPP 下的 Stripping Level 设置)
  • 减少对不同模块和库的依赖

Graphics (Estimated)(图形相关,估算)

指的是图形驱动和 GPU 为渲染应用程序而使用的估算内存。这些信息基于 Unity 内部对图形资源分配情况的追踪得出,包括:

  • RenderTexture
  • Texture
  • Mesh
  • Animation
  • 以及其它通过 Unity 或脚本 API 分配的图形缓冲区(Graphics Buffers)

你可以在 All Of Memory 标签页中进一步查看这些图形资源。

注意事项:

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

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

Untracked*(未追踪内存)

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) 图形内存的大小,因为:

  1. 我们知道图形设备会申请某些类型的内存区域
  2. 但无法精确追踪每个图形资源具体映射到哪个内存块
  3. 所以直接从图形设备相关区域中减去 Graphics(Estimated) 的总值
  4. 若找不到图形区域,则从最大的几个内存区域中扣减这部分图形估算值

建议:

  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 是否频繁回收、是否发生堆扩容等问题

其中包括:

  1. Empty Heap Space(空闲堆空间)
    • 可能是之前被对象使用过,或者在上一次堆扩展时预留的空间
    • 托管堆中空闲区域,可能是旧对象被 GC 清理后的空位,或堆扩容后的未使用空间
  2. Virtual Machine(虚拟机使用的内存)
    • 脚本运行时(Mono / IL2CPP)自身占用的非对象内存,比如类型元数据、静态字段、泛型支持等
  3. Objects(托管对象占用内存)
    • 托管堆中正在被引用的托管对象占用的内存,GC 不会清除的内存

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

  1. 托管对象(Managed Objects)所占内存
    • 对比多个快照,看是不是存在对象一直不被释放,此时可能存在内存泄漏
    • 或者看看对象创建速度是否超过了 GC 回收速度
  2. 空闲堆内存在 GC 触发后,是否有明显回收、收缩(即内存是否被释放出来)
  3. 虚拟机内存是否不断增大
    • 如果存在:可能是元数据膨胀或静态字段泄漏(静态字段持有大量对象未释放),特别注意单例
  4. 不要单独只看一个数字,要结合堆大小、使用率、类型分布、引用关系综合判断

比较观察:

  1. 对比托管对象所占内存和堆内存
    • 对象很少但堆很大:说明堆膨胀,可能存在内存碎片
  2. Empty Heap Space 高,已 GC 但堆未收缩:可能是 GC 没触发或碎片多
  3. Virtual Machine 增长快:泛型类过多或静态字段持有大对象未释放

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

详细解析

Empty Heap Space(空闲堆空间)

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

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

  1. 首先检查当前活动堆区段中是否有空闲空间
    • 这是分配内存最快的方式(直接连续分配)
  2. 如果活动堆区段中没有足够的连续空闲空间
    • 脚本虚拟机(Scripting VM)就需要扫描“空闲块列表”和其它堆区段来寻找可用空间,这一步更慢
  3. 如果还是找不到合适的位置,会触发垃圾回收(GC.Collection)
    • 启用增量式 GC(Incremental GC):找不到空间时会立刻分配一个新的堆区段(扩展堆)存放新对象,同时垃圾回收异步进行
    • 未启用增量式 GC:系统会先尝试做一次完整的 GC,如果回收后仍没有足够空间,才会新分配堆区段

另外,这部分内存中也可能仍然包含一些已“被放弃”的对象:上一次 GC 后已无引用的对象,但尚未被实际清理,等待下次 GC 时才会被回收。

重点排查:

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

Virtual Machine(虚拟机)

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

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

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

尚未初始化的类型指:没有任何代码访问过它们的类型相关数据,因此它们的静态构造函数(无论显式还是隐式)尚未被调用。
这种情况通常出现在数组或泛型集合类中——虽然集合被创建并使用了,但其内部类型数据还没有被实际访问。

重点:

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

Objects(托管对象)

用于托管对象(Managed Objects)的内存:由 C# 脚本代码中定义的类型创建和使用的对象所占用的内存。这个数字仅包括仍然被引用或保持存活状态的对象的内存。

尚未被回收器(Garbage Collector)收集的托管对象,可能位于活动堆区段的空闲区域(Empty Active / Fragmented Heap Space)中。

重点:

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

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

主要作用

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

常见类型含义:

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

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

Summary(总结概要页签)对于我们的意义

Summary 页签给到的是“总览视角”,常用价值点:

  • Memory Usage On Device:判断应用是否接近或超出设备内存上限
  • Allocated Memory Distribution:快速识别主要内存消耗源头
  • Managed Heap Utilization:用于分析 C# 脚本对象的内存使用效率
  • Top Unity Objects Categories:针对某类型内容优化的数据依据之一

着重关注以下内容:

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

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


10.2 知识点代码

Lesson10_Unity性能分析工具_MemoryProfiler窗口Summary.cs

public class Lesson10_Unity性能分析工具_MemoryProfiler窗口Summary
{
    #region 知识点一 Summary(总结摘要)的主要内容

    /*
     * Summary(总结摘要)
     * - 该页签主要显示快照中的内存总体信息
     * - 主要由以下四部分组成:
     *   1. Memory Usage On Device(设备上的内存使用情况)
     *   2. Allocated Memory Distribution(已分配内存分布)
     *   3. Managed Heap Utilization(托管堆使用率)
     *   4. Top Unity Objects Categories(Unity 对象内存占用最多的类型分类)
     */

    #endregion

    #region 知识点二 Memory Usage On Device(设备上的内存使用情况)

    /*
     * 主要作用:
     * - 展示当前快照中,Unity 在设备上实际占用了多少内存
     * - 用于判断是否接近设备内存限制,尤其移动端内存溢出风险
     *
     * 补充:内存溢出(OOM)
     * - iOS / Android 会给每个 App 分配最大内存上限(如 512MB、1GB 等,依设备不同而异)
     * - 若运行时内存分配超出上限,系统可能强行杀死进程,不给任何提示
     *
     * 一些统计数据(不准确,以真机为主):
     * - Android 低端机:~350–450MB
     * - Android 中端机:~500–600MB
     * - Android 高端机:~700MB–1.2GB
     * - iPhone 6s:~350MB
     * - iPhone 12+:~1GB+
     * - iPad Pro:~1.5GB+
     *
     * 注意:
     * - 建议在真机上进行调试排查
     */

    #endregion

    #region 知识点三 Allocated Memory Distribution(已分配内存分步)

    /*
     * 主要作用:
     * - 显示分配的内存如何在不同内存区域中分布
     * - 将光标悬停在类别上,可查看分配的内存中有多少当前驻留在设备上
     * - 用于分析各系统的内存占用比例,判断到底是谁“吃”内存
     *
     * 主要分类:
     * 1. Native(原生内存):C++ 层对象(GameObject、资源加载、系统插件等)
     * 2. Managed(托管内存):C# 托管对象(类、数组、List 等)
     * 3. Executables & Mapped:可执行代码和库(模块太多可考虑 Stripping)
     * 4. Graphics (Estimated):图形相关(估算)(纹理、Mesh、Animation 等)
     * 5. Untracked*:未追踪内存(常需平台专用工具进一步分析)
     */

    #endregion

    #region 知识点四 Managed Heap Utilization(托管堆使用率)

    /*
     * 主要作用:
     * - 显示 Scripting VM 所管理的内存分类,用于分析托管堆效率与 GC 行为
     *
     * 组成:
     * 1. Empty Heap Space:空闲堆空间(旧对象回收后的空位 / 扩容预留)
     * 2. Virtual Machine:虚拟机内存(类型元数据、静态字段、泛型支持等)
     * 3. Objects:活跃托管对象占用内存
     *
     * 排查关注点:
     * - Managed Objects 是否持续增长(可能泄漏 / 创建过快)
     * - GC 后空闲堆是否回收、堆是否收缩
     * - Virtual Machine 是否持续增长(元数据膨胀 / 静态字段泄漏,注意单例)
     * - 不看单一数值,结合堆大小、使用率、类型分布、引用关系综合判断
     */

    #endregion

    #region 知识点五 Top Unity Objects Categories(Unity对象内存占用最多的类型分类)

    /*
     * 主要作用:
     * - 显示当前快照中哪些 Unity 类型对象占用最多内存
     * - 用于决定优化方向(例如优先从 RenderTexture / Texture2D 入手)
     *
     * 常见项示例:
     * - RenderTexture:渲染纹理(后处理、相机输出、截图、倒影、UI 等)
     * - Texture2D:静态纹理(材质贴图)
     * - Shader:着色器(含变体)
     * - SceneVisibilityState:编辑器“场景可见性”结构(编辑器特有)
     * - MonoManager:Unity 内部脚本生命周期管理器
     * - Others:其它未归类对象(如原生插件对象等)
     *
     * 详细信息可前往 Unity Objects 页签查看
     */

    #endregion

    #region 知识点六 对于我们的意义

    /*
     * Summary 页签对于我们的意义:
     * - 判断应用是否接近/超出设备内存上限
     * - 快速识别主要内存消耗源头
     * - 分析托管堆效率与 GC 压力
     * - 给出按类型优化的依据
     *
     * 着重关注:
     * 1. 托管堆:GC 压力、碎片化
     * 2. 原生内存:Unity 原生对象泄漏(GameObject、Component、资源等)
     * 3. 图形内存:贴图 / RenderTexture 等异常
     * 4. 虚拟机内存:增长趋势
     * 5. 未追踪内存:异常大块需要平台工具辅助
     * 6. 预留但未使用的内存:是否碎片化
     * ……等等
     *
     * 可通过对比多张内存快照来排查问题
     */

    #endregion
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏