版本:Unity 6 (6000.0)
语言:English
Unity 中的内存
垃圾收集器概述

托管内存

Unity 的托管内存系统是基于Mono 或 IL2CPP 虚拟机 (VM) 的 C# 脚本环境。托管内存系统的优势在于它管理内存的释放,因此您无需通过代码手动请求释放内存。

Unity 的托管内存系统使用垃圾收集器托管堆 在您的脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间推移修改组件属性并以任何您喜欢的方式响应用户输入。 更多信息
参见 术语表
不再持有对这些分配的任何引用时自动释放内存分配。这有助于防止内存泄漏。内存泄漏发生在分配内存、对它的引用丢失,然后内存永远不会被释放,因为它需要一个引用来释放它。

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

值类型和引用类型

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

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

熟悉值类型和引用类型非常重要,以便您可以有效地管理代码。有关更多信息,请参阅 Microsoft 关于值类型引用类型的文档。

自动内存管理

当创建对象时,Unity 会从称为的中央池中分配存储对象所需的内存,堆是 Unity 项目所选脚本运行时 (Mono 或 IL2CPP) 自动管理的内存区域。当不再使用对象时,它曾经占据的内存可以被回收并用于其他用途。

Unity 的脚本后端使用垃圾收集器自动管理应用程序的内存,因此您无需使用显式方法调用分配和释放这些内存块。自动内存管理比显式分配/释放需要更少的编码工作,并减少了内存泄漏的可能性。

托管堆概述

托管堆是 Unity 项目所选脚本运行时 (Mono 或 IL2CPP) 自动管理的内存区域。

A quantity of memory. Marked A on the diagram is some free memory.
一定数量的内存。图中标记为 A 的是一些空闲内存。

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

内存碎片和堆扩展

A quantity of memory, with some objects released represented by grey dashed lines.
一定数量的内存,其中一些已释放的对象用灰色虚线表示。

上图显示了内存碎片的一个示例。当 Unity 释放对象时,对象占据的内存会被释放。但是,空闲空间不会成为单个大型“空闲内存”池的一部分。

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

这种情况称为内存碎片。当堆中有大量可用内存,但它仅在对象之间的“间隙”中可用时,就会发生这种情况。这意味着即使有足够的总空间进行大型内存分配,托管堆也无法找到足够大的连续内存块来分配给该分配。

The object annotated A, is the new object needed to be added to the heap. The items annotated B are the memory space that the released objects took up, plus the free, unreserved memory. Even though there is enough total free space, because there isnt enough contiguous space, the memory for the new object annotated A cant fit on the heap, and the garbage collector must run.
注释为 A 的对象是要添加到堆中的新对象。注释为 B 的项目是已释放对象占据的内存空间,加上空闲的、未保留的内存。即使有足够的总空闲空间,但由于没有足够的连续空间,注释为 A 的新对象的内存无法放入堆中,垃圾收集器必须运行。

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

  • 首先,垃圾收集器运行,如果它还没有运行。这尝试释放足够的内存来满足分配请求。
  • 如果在垃圾收集器运行后,仍然没有足够的连续空间来容纳请求的内存量,则堆必须扩展。堆扩展的具体数量取决于平台;但是,在大多数平台上,当堆扩展时,它会扩展到之前扩展量的两倍。

托管堆扩展注意事项

堆的意外扩展可能存在问题。Unity 的垃圾回收策略往往会更频繁地碎片化内存。您应该注意以下几点

  • Unity 不会定期释放分配给托管堆的内存;相反,它会保留扩展的堆,即使其中很大一部分是空的。这样做是为了防止在进一步发生大型分配时需要重新扩展堆。
  • 在大多数平台上,Unity 最终会将托管堆空闲部分使用的内存释放回操作系统。发生这种情况的间隔没有保证且不可靠。
  • 垃圾收集器不会清除本机内存对象或其他本机分配。Resources.UnloadUnusedAssets 会为任何不再有任何引用指向它的本机对象执行此操作。它还会触发 GC.Collect 以确保这些引用的状态是最新的。请记住
    • UnloadUnusedAssets 不会自动触发,仅手动触发并在场景场景包含游戏环境和菜单。将每个唯一的场景文件视为一个唯一的关卡。在每个场景中,您放置环境、障碍物和装饰,本质上是分段设计和构建您的游戏。 更多信息
      参见 术语表
      更改时触发。如果您想更早地释放该内存,这对于例如具有低 RAM 的平台上的全屏 RenderTexture 至关重要,您应该在对象上调用 Destroy 以优化您拥有的内存的使用。
    • 存在托管内存泄漏的风险。如果您保留了对已清理对象的引用,则您有泄漏托管对象引用的本机对象的风险。静态字段和事件是此类内存泄漏的常见来源。有关如何使用内存分析器一个帮助您优化游戏的窗口。它显示在游戏的各个区域花费了多少时间。例如,它可以报告渲染、动画或游戏逻辑中花费的时间百分比。 更多信息
      参见 术语表
      分析这些问题的更多信息,请参阅内存分析器软件包文档中的托管 Shell 对象
    • 调用 UnloadUnusedAssetsGC.Collect 会触发一个 CPU 密集型过程,您可能希望在游戏过程中避免此过程。
Unity 中的内存
垃圾收集器概述