# 托管内存

> Automatically manage the release and allocation of memory in your application.

Unity 的**托管内存系统**是基于 [Mono 或 IL2CPP 虚拟机 (VM)](/engine/6000.0/manual/scripting/compilation-and-code-reload/backends.md) 的 C# 脚本环境。托管内存系统的优势在于可以管理内存释放，因此无需通过代码手动请求释放内存。

Unity 的托管内存系统使用[垃圾收集器](/engine/6000.0/manual/analysis/performance-memory-overview/managed-memory/garbage-collector.md)和[托管堆](#managed-heap)，在脚本不再包含对内存分配的任何引用时自动释放这些分配。这有助于防止内存泄漏。内存泄漏发生在分配了内存但对内存的引用丢失时，之后因为需要[引用](#value-reference)内存才能释放内存，所以永远不会释放该内存。

此内存管理系统还会保护内存访问，这意味着无法访问已释放的内存或代码从未有效访问的内存。但是，此内存管理过程会影响运行时性能，因为分配托管内存对 CPU 而言是一项耗时的工作。[垃圾收集](/engine/6000.0/manual/analysis/performance-memory-overview/managed-memory/garbage-collector.md)也可能使 CPU 在垃圾收集完成之前停止执行其他工作。

## 值和引用类型

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

托管代码中的所有非 null [引用类型对象](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/reference-types)和所有[装箱值类型对象](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing)必须分配在托管堆上。

熟悉值和引用类型非常重要，这样才能有效管理代码。有关更多信息，请参阅 Microsoft 有关[值类型](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types)和[引用类型](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/reference-types)的文档。

## 自动内存管理

创建对象时，Unity 会从称为**堆**的中央池中分配存储对象所需的内存，这是 Unity 项目的选定脚本运行时（Mono 或 IL2CPP）自动管理的一段内存。当此对象不再使用时，先前占用的内存可被回收并用于其他对象。

[Unity 的脚本后端](/engine/6000.0/manual/scripting/compilation-and-code-reload/backends.md)使用[垃圾收集器](/engine/6000.0/manual/analysis/performance-memory-overview/managed-memory/garbage-collector.md)来自动管理应用程序的内存，因此无需使用显式方法调用来分配和释放这些内存块。相比显式分配/释放，自动内存管理减少了所需的编码工作量，并且降低了内存泄漏的可能性。

### 托管堆概览

**托管堆**是由 Unity 项目的选定脚本运行时（Mono 或 IL2CPP）自动管理的一段内存。


**内存量。图中标记 A 表示部分可用内存。:**
![](/api/media?file=/engine/6000.0/media/images/managed-heap.jpg)

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

### 内存碎片化和堆扩展


**内存量，已释放的一些对象由灰色虚线表示。:**
![](/api/media?file=/engine/6000.0/media/images/managed-heap-removed-objects.jpg)

上图为内存碎片化示例。Unity 释放对象时，对象占用的内存也将被释放。但是，释放的空间不会整合成为整个“可用内存”池的一部分。

位于释放对象两侧的对象可能仍在使用中。因此，释放的空间是其他内存段之间的“空隙”。Unity 只能使用此空隙来存储与释放的对象大小相同或更小的数据。

这种情况称为**内存碎片化**。当堆中有大量可用内存，但仅在对象之间的“空隙”中可用时，就会发生这种情况。这意味着，即使有足够的空间总量用于大型内存分配，托管堆也无法找到足够大的单个连续内存块并将其指定用于分配。


**注释为 A 的对象是需要添加到堆中的新对象。注释为 B 的项是已释放的对象占用的内存空间，加上可用的未保留内存。尽管有足够的可用空间总量，但由于没有足够的连续空间，所以注释为 A 的新对象的内存无法放入堆中，垃圾收集器必须运行。:**
![](/api/media?file=/engine/6000.0/media/images/managed-heap-fragmentation.jpg)

如果分配了大型对象，却没有足够的连续可用空间来容纳该对象（如上所示），Unity 内存管理器将执行两个操作：

* 首先，如果垃圾收集器尚未运行，则运行垃圾收集器。此工具会尝试释放足够的空间来满足分配请求。
* 如果在垃圾收集器运行后，仍然没有足够的连续空间来满足请求的内存量，则必须扩展堆。具体的堆扩展量取决于平台；但是，在大多数平台上，当堆扩展时，其扩展量是上次扩展量的两倍。

### 托管堆扩展注意事项

意外扩展堆可能导致问题。Unity 的垃圾收集策略往往会更频繁地使内存碎片化。应注意以下事项：

* Unity 在常规扩展时不会释放分配给托管堆的内存；相反，即使堆的大部分为空，它也会保留扩展后的堆。这是为了防止再次出现大型分配时需要重新扩展堆。

* 在大多数平台上，Unity 最终会将托管堆的空置部分使用的内存释放回操作系统。发生这种情况的时间间隔无法保证，也不会保持一致。

* 垃圾收集器不会清除本机内存对象或其他本机分配。[Resources.UnloadUnusedAssets](/engine/6000.0/script-reference/unity-engine/resources/unload-unused-assets.md) 对任何不再有任何引用的本机对象执行此操作。它还会触发一个 `GC.Collect` 以确保这些引用的状态是最新的。请记住：

  * `UnloadUnusedAssets` 不会自动触发，只会手动触发和在场景变化时触发。如果要提前释放该内存（例如，对于低 RAM 平台上的全屏渲染纹理，可能必须执行此操作），则应在对象上调用 [Destroy](/engine/6000.0/script-reference/unity-engine/object/destroy.md)，以最佳方式利用内存。
  * 存在托管内存泄漏的风险。如果保留了对已清理对象的引用，则可能泄漏托管对象引用的本机对象。静态字段和事件是此类内存泄漏的常见来源。有关如何使用 Memory Profiler 分析这些问题的更多信息，请参阅 Memory Profiler 包文档中的[托管 shell 对象](https://docs.unity3d.com/Packages/com.unity.memoryprofiler@1.1/manual/managed-shell-objects.html)。
  * 调用 `UnloadUnusedAssets` 或 `GC.Collect` 会触发可能希望在游戏过程中避免的 CPU 密集型操作。
