Unity3D:内存托管

推荐:将NSDT场景编辑器加入你的3D工具链
3D工具集:NSDT简石数字孪生
内存托管
Unity 的托管内存系统是基于 Mono 或 IL2CPP 虚拟机 (VM) 的 C# 脚本环境。托管内存系统的好处是它管理内存的释放,因此您无需通过代码手动请求释放内存。
Unity 的托管内存系统使用垃圾回收器和托管堆,以便在脚本不再包含对这些分配的任何引用时自动释放内存分配。这有助于防止内存泄漏。当分配内存时,会发生内存泄漏,对它的引用丢失,然后永远不会释放内存,因为它需要对它的引用来释放它。
此内存管理系统还保护内存访问,这意味着您无法访问已释放的内存,或者代码访问的内存永远无效。但是,此内存管理过程会影响运行时性能,因为分配托管内存对于 CPU 来说非常耗时。垃圾回收还可能阻止 CPU 执行其他工作,直到完成。
值和引用类型
调用方法时,脚本后端将其参数的值复制到为该特定调用保留的内存区域,该区域位于称为调用堆栈的数据结构中。脚本后端可以快速复制占用几个字节的数据类型。但是,对象、字符串和数组通常要大得多,并且脚本后端定期复制这些类型的数据效率低下。
必须在托管堆上分配托管代码中的所有非 null 引用类型对象和所有装箱值类型的对象。
熟悉值和引用类型非常重要,这样才能有效地管理代码。有关详细信息,请参阅 Microsoft 有关值类型和引用类型的文档。
自动内存管理
创建对象时,Unity 会从称为堆的中央池中分配存储该对象所需的内存,堆是 Unity 项目所选脚本运行时(Mono 或 IL2CPP)自动管理的内存部分。当一个对象不再使用时,它可以回收它曾经占用的内存并用于其他内容。
Unity 的脚本后端使用垃圾回收器自动管理应用程序的内存,因此您无需通过显式方法调用来分配和释放这些内存块。与显式分配/释放相比,自动内存管理需要更少的编码工作,并减少了内存泄漏的可能性。
托管堆概述
托管堆是 Unity 项目所选脚本运行时(Mono 或 IL2CPP)自动管理的内存部分。

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

上图显示了内存碎片的示例。当 Unity 释放对象时,将释放该对象占用的内存。但是,可用空间不会成为单个大型“可用内存”池的一部分。
释放对象两侧的对象可能仍在使用中。因此,释放的空间是其他内存段之间的“间隙”。Unity 只能使用此间隙来存储与发布对象相同或更小大小的数据。
这种情况称为内存碎片。当堆中有大量可用内存,但仅在对象之间的“间隙”中可用时,就会发生这种情况。这意味着,即使有足够的总空间用于大型内存分配,托管堆也无法找到足够大的单个连续内存块来分配给分配。

如果分配了一个大型对象,并且没有足够的连续可用空间来容纳它,如上图所示,Unity 内存管理器将执行两个操作:
- 首先,垃圾回收器运行(如果尚未运行)。这将尝试释放足够的空间来满足分配请求。
- 如果在垃圾回收器运行后,仍然没有足够的连续空间来容纳请求的内存量,则必须扩展堆。堆扩展的具体量取决于平台;但是,在大多数平台上,当堆扩展时,它会扩展为先前扩展量的两倍。
托管堆扩展注意事项
堆的意外扩展可能会出现问题。Unity 的垃圾回收策略倾向于更频繁地对内存进行碎片化。您应该注意以下几点:
- Unity 在定期扩展时不会释放分配给托管堆的内存;相反,它保留扩展的堆,即使其中很大一部分是空的。这是为了防止在发生进一步的大分配时需要重新扩展堆。
- 在大多数平台上,Unity 最终会将托管堆的空部分使用的内存释放回操作系统。无法保证发生这种情况的时间间隔,并且不可靠。
此文由3D建模学习工作室整理翻译,转载请注明出处!