包含此页的版本:
不含此页的版本:
Unity 的托管内存系统是 Mono 或 IL2CPP 虚拟机 (VM) 提供的 C# 脚本环境的一部分。托管内存系统的好处是它管理内存的释放,因此您无需通过代码手动请求释放内存。这些 VM 提供由以下部分组成的受控内存环境:
Unity 的托管内存系统使用垃圾收集器和托管堆来自动释放内存分配,当您脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间修改组件属性以及以您喜欢的任何方式响应用户输入。更多信息
请参阅术语表不再包含对这些分配的任何引用。这有助于防止内存泄漏,当分配的内存永远不会被释放时,就会发生这种情况,因为手动释放它所需的引用已经丢失。
Unity 的内存管理系统保护内存访问,这意味着您无法访问已释放的内存,或者您的代码从未访问过的内存。但是,此内存管理过程会影响运行时性能,因为分配托管内存对 CPU 来说非常耗时。垃圾回收还可能阻止 CPU 执行其他工作,直到它完成。
在下图中,蓝色框表示 Unity 分配给托管堆的内存量。其中的白框表示 Unity 存储在托管堆内存空间中的数据值。当需要其他数据值时,Unity 会从托管堆(带注释的 A)中为它们分配可用空间。
调用方法时,脚本后端将其参数的值复制到为该特定调用保留的内存区域,该区域称为调用堆栈的数据结构中。脚本后端可以快速复制占用几个字节的数据类型。但是,对象、字符串和数组通常要大得多,脚本后端定期复制这些类型的数据效率低下。
托管代码中的所有非 null 引用类型对象和所有装箱值类型对象都必须在托管堆上分配。
熟悉值和引用类型非常重要,这样才能有效地管理代码。有关详细信息,请参阅 Microsoft 关于值类型和引用类型的文档。
当 Unity 释放对象时,该对象占用的内存将被释放。但是,可用空间不会成为单个大型可用内存池的一部分。
已释放对象两侧的对象可能仍在使用中。因此,释放的空间是其他内存段之间的间隙。Unity 只能使用此间隙来存储与已发布对象大小相同或更小的数据。这种情况称为内存碎片,下图显示了一个示例:
当堆中有大量可用内存时,就会发生内存碎片,但仅在对象之间的内存间隙中可用。即使有足够的总空间进行大型内存分配,托管堆也找不到足够大的连续内存块来分配给分配。
如果分配了一个大型对象,并且没有足够的连续可用空间来容纳它,则 Unity 内存管理器将执行两个作:
当托管堆定期扩展时,Unity 不会释放分配给托管堆的内存。相反,它会保留扩展的堆,即使堆的很大一部分是空的。这样可以防止在发生进一步的大型分配时重新扩展堆。
在大多数平台上,Unity 最终会将托管堆的空部分使用的内存释放回作系统。无法保证发生这种情况的时间间隔,并且不可靠。这要求这些部分由一个或多个完整内存页组成(在大多数平台上为 4 KB)。
垃圾回收器不会清除本机内存对象或其他本机分配。Unity 仅在以下情况下清除本机内存:
Resources.UnloadUnusedAssets被称为。您可以手动调用UnloadUnusedAssets,或者 Unity 在卸载场景场景包含游戏的环境和菜单。将每个唯一的场景文件视为一个独特的关卡。在每个场景中,你放置你的环境、障碍物和装饰品,基本上是将你的游戏设计和构建成碎片。更多信息LoadSceneMode.Single.对于异步场景卸载,您可以使用SceneManager.UnloadSceneAsync和选项UnloadSceneOptions.UnloadAllEmbeddedSceneObjects.叫SceneManager.UnloadSceneAsync跟UnloadSceneOptions.UnloadAllEmbeddedSceneObjects不会卸载 Unity 加载以在场景中使用的常规资源,也不会卸载动态和运行时创建的资源。Destroy在对象上调用。
Resources.UnloadUnusedAssets卸载所有不再有任何指向它们的引用的本机对象。如果您想提前释放内存,例如,如果您拥有全屏RenderTextureRAM 较低的平台上的实例,调用Destroy在对象上。
如果你想更好地控制保留在内存中的资产,请使用 AssetBundles 和 Addressables。
重要:叫UnloadUnusedAssets或GC.Collect是一个 CPU 密集型进程。在大型项目中,可能需要几秒钟才能完成
在调用之前UnloadUnusedAssets,请确保您没有持有对要清理的本机对象的托管引用,否则本机对象将不会被卸载。静态字段和事件是无意中保留这些对象的常见方法。有关如何使用内存探查器分析这些问题的详细信息,请参阅内存探查器包文档中的托管 shell 对象。