Version: 6000.3
语言: 中文
托管内存
垃圾回收器概述

托管内存介绍

Unity 的托管内存系统是 Mono 或 IL2CPP 虚拟机 (VM) 提供的 C# 脚本环境的一部分。托管内存系统的好处是它管理内存的释放,因此您无需通过代码手动请求释放内存。这些 VM 提供由以下部分组成的受控内存环境:

  • 托管堆:VM 使用垃圾回收器自动控制的内存部分。在托管堆上分配的内存称为 GC 分配,而分析器帮助您优化游戏的窗口。它显示了在游戏的各个领域花费了多少时间。例如,它可以报告渲染、动画制作或游戏逻辑所花费的时间百分比。更多信息
    请参阅术语表
    记录此类分配的任何发生,如 GC。分配样本。
  • 脚本堆栈:这是在每个线程开始时分配的固定大小的内存区域。当应用程序移入和移出代码块时,它会跟踪变量和执行流等临时数据。
  • 本机 VM 内存:包含与 Unity 脚本 VM 的执行和管理相关的内存。您无法直接访问它,代码的正常执行会间接影响它。了解它包括与代码生成的可执行代码相关的内存,特别是用于泛型的内存、用于反射的类型元数据以及运行 VM 所需的内存,这很有用。

自动内存管理

Unity 的托管内存系统使用垃圾收集器和托管堆来自动释放内存分配,当您脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间修改组件属性以及以您喜欢的任何方式响应用户输入。更多信息
请参阅术语表
不再包含对这些分配的任何引用。这有助于防止内存泄漏,当分配的内存永远不会被释放时,就会发生这种情况,因为手动释放它所需的引用已经丢失。

Unity 的内存管理系统保护内存访问,这意味着您无法访问已释放的内存,或者您的代码从未访问过的内存。但是,此内存管理过程会影响运行时性能,因为分配托管内存对 CPU 来说非常耗时。垃圾回收还可能阻止 CPU 执行其他工作,直到它完成。

在下图中,蓝色框表示 Unity 分配给托管堆的内存量。其中的白框表示 Unity 存储在托管堆内存空间中的数据值。当需要其他数据值时,Unity 会从托管堆(带注释的 A)中为它们分配可用空间。

内存量。图上标记为 A 的是一些可用内存。
内存量。图上标记为 A 的是一些可用内存。

分配给堆的类型

调用方法时,脚本后端将其参数的值复制到为该特定调用保留的内存区域,该区域称为调用堆栈的数据结构中。脚本后端可以快速复制占用几个字节的数据类型。但是,对象、字符串和数组通常要大得多,脚本后端定期复制这些类型的数据效率低下。

托管代码中的所有非 null 引用类型对象和所有装箱值类型对象都必须在托管堆上分配。

熟悉值和引用类型非常重要,这样才能有效地管理代码。有关详细信息,请参阅 Microsoft 关于值类型引用类型的文档。

内存碎片和堆扩展

当 Unity 释放对象时,该对象占用的内存将被释放。但是,可用空间不会成为单个大型可用内存池的一部分。

已释放对象两侧的对象可能仍在使用中。因此,释放的空间是其他内存段之间的间隙。Unity 只能使用此间隙来存储与已发布对象大小相同或更小的数据。这种情况称为内存碎片,下图显示了一个示例:

内存量,释放的一些对象用灰色虚线表示。
内存量,释放的一些对象用灰色虚线表示。

当堆中有大量可用内存时,就会发生内存碎片,但仅在对象之间的内存间隙中可用。即使有足够的总空间进行大型内存分配,托管堆也找不到足够大的连续内存块来分配给分配。

如果分配了一个大型对象,并且没有足够的连续可用空间来容纳它,则 Unity 内存管理器将执行两个作:

  1. 垃圾回收器将运行(如果尚未运行)。这会尝试释放足够的空间来满足分配请求。
  2. 如果垃圾回收器运行后,仍然没有足够的连续空间来容纳请求的内存量,则堆必须扩展。堆扩展的具体数量取决于平台。在大多数平台上,当堆扩展时,它会扩展前一个扩展量的两倍。如果请求的分配大小大于该大小,Unity 将执行周期外扩展以适应该分配,并从之前的常规扩展大小继续,作为下一次扩展的基础。

当托管堆定期扩展时,Unity 不会释放分配给托管堆的内存。相反,它会保留扩展的堆,即使堆的很大一部分是空的。这样可以防止在发生进一步的大型分配时重新扩展堆。

在大多数平台上,Unity 最终会将托管堆的空部分使用的内存释放回作系统。无法保证发生这种情况的时间间隔,并且不可靠。这要求这些部分由一个或多个完整内存页组成(在大多数平台上为 4 KB)。

托管堆和本机内存

垃圾回收器不会清除本机内存对象或其他本机分配。Unity 仅在以下情况下清除本机内存:

  • 什么时候Resources.UnloadUnusedAssets被称为。您可以手动调用UnloadUnusedAssets,或者 Unity 在卸载场景场景包含游戏的环境和菜单。将每个唯一的场景文件视为一个独特的关卡。在每个场景中,你放置你的环境、障碍物和装饰品,基本上是将你的游戏设计和构建成碎片。更多信息
    请参阅术语表
    由于在LoadSceneMode.Single.对于异步场景卸载,您可以使用SceneManager.UnloadSceneAsync和选项UnloadSceneOptions.UnloadAllEmbeddedSceneObjects.叫SceneManager.UnloadSceneAsyncUnloadSceneOptions.UnloadAllEmbeddedSceneObjects不会卸载 Unity 加载以在场景中使用的常规资源,也不会卸载动态和运行时创建的资源。
  • 什么时候Destroy在对象上调用。

Resources.UnloadUnusedAssets卸载所有不再有任何指向它们的引用的本机对象。如果您想提前释放内存,例如,如果您拥有全屏RenderTextureRAM 较低的平台上的实例,调用Destroy在对象上。

如果你想更好地控制保留在内存中的资产,请使用 AssetBundles 和 Addressables

重要:UnloadUnusedAssetsGC.Collect是一个 CPU 密集型进程。在大型项目中,可能需要几秒钟才能完成

在调用之前UnloadUnusedAssets,请确保您没有持有对要清理的本机对象的托管引用,否则本机对象将不会被卸载。静态字段和事件是无意中保留这些对象的常见方法。有关如何使用内存探查器分析这些问题的详细信息,请参阅内存探查器包文档中的托管 shell 对象

其他资源

托管内存
垃圾回收器概述