前言
Unity3D 的内存管理机制涉及 托管堆(Managed Heap) 和 非托管堆(Unmanaged Heap) 两部分,分别对应 C# 代码和 Unity 引擎底层的内存管理。以下是其底层原理的详细分析:
对惹,这里有一个游戏开发交流小组 ,大家可以点击进来一起交流一下开发经验呀!
1. 托管堆(Managed Heap)
托管堆由 C# 的垃圾回收器(Garbage Collector, GC) 管理,用于存储 C# 对象(如类实例、数组等)。
内存分配
- 当创建
class实例或引用类型对象(如List<int>)时,内存从托管堆分配。 - 分配流程:
- 首次分配:堆内存按块(Segments)划分,每个块分为多个对象槽。
- 快速分配:新对象优先分配在空闲区域(称为“分配指针”位置)。
- 空间不足时:触发垃圾回收(GC)或扩展堆内存(向操作系统申请新内存块)。
垃圾回收(GC)
-
分代回收机制:托管堆分为 0代(年轻代)、1代(中间代)、2代(老年代) 。
-
- 新对象分配在 0 代。
- 若对象在一次 GC 后存活,则晋升到下一代。
- 0 代 GC 最频繁(速度快),2 代 GC 最耗时(涉及全堆扫描)。
-
GC 流程:
- 标记阶段:从根对象(静态变量、活动线程栈等)出发,标记所有可达对象。
- 清除阶段:回收未被标记的对象内存。
- 压缩阶段(可选):整理内存碎片,合并空闲空间(可能影响性能)。
托管堆的问题
- 内存碎片:频繁分配/释放可能导致碎片化,降低内存利用率。
- GC 卡顿:2 代 GC 会导致主线程暂停(影响帧率)。
2. 非托管堆(Unmanaged Heap)
非托管堆由 Unity 引擎底层(C++) 管理,存储引擎资源(如纹理、网格、音频等)和 Native 代码分配的内存。
内存分配
-
Unity 通过 引用计数(Reference Counting) 或 显式释放 管理非托管资源。
-
- 例如:
Texture2D、GameObject等UnityEngine.Object子类。
- 例如:
-
Native 内存分配:通过 C/C++ 的
malloc/new或 Unity API(如Texture2D.Create)分配。
回收机制
-
引用计数:
-
- 当
UnityEngine.Object的引用计数归零时,引擎标记为可回收。 - 实际释放时机由引擎决定(可能延迟到帧末尾或场景切换)。
- 当
-
显式释放:
-
- 调用
Resources.UnloadUnusedAssets()或AssetBundle.Unload(true)。 - 使用
Destroy或DestroyImmediate销毁对象(后者立即释放,但需谨慎使用)。
- 调用
3. Unity 内存管理的核心问题
托管与非托管的交互
- 托管对象引用非托管资源:例如
Texture2D在 C# 层是托管对象,但实际数据存储在非托管堆。 - 内存泄漏:若托管层代码持有对非托管资源的引用(如未释放
AssetBundle),会导致资源无法回收。
常见性能问题
- 频繁 GC 触发:在
Update中频繁分配堆内存(如new对象、字符串拼接)。 - 非托管资源泄漏:未正确卸载
AssetBundle或未销毁UnityEngine.Object。 - 内存碎片:大量小对象分配导致托管堆碎片化。
4. 优化策略
减少 GC 压力
-
避免频繁堆分配:
-
- 使用对象池(Object Pool)复用对象(如子弹、粒子特效)。
- 使用
struct替代class(值类型分配在栈上,不触发 GC)。 - 避免在循环中分配内存(如缓存集合、预分配数组)。
-
控制 GC 触发时机:
-
- 手动调用
System.GC.Collect()(建议在加载场景时触发)。
- 手动调用
管理非托管资源
-
及时释放资源:
-
- 调用
Resources.UnloadUnusedAssets()释放未引用资源。 - 销毁不再使用的
GameObject(Destroy)或释放AssetBundle。
- 调用
-
引用管理:
-
- 避免静态变量长期持有资源引用。
工具辅助
- Unity Profiler:监控内存使用(
Memory > Detailed视图)。 - Heap Explorer:分析托管堆碎片和对象分布。
- Addressables:替代
Resources系统,更灵活管理资源生命周期。
5. 底层机制补充
- IL2CPP 的影响:Unity 将 C# 代码转换为 C++ 时,托管堆的 GC 由 Boehm GC 或 增量式 GC(2021+ 版本)管理,优化了跨平台性能。
- Job System 与 Burst:使用 Unity 的 ECS 和 Burst 编译器可减少托管堆分配,提升性能。
总结
Unity 的内存管理是托管与非托管的混合模式,开发者需同时关注 C# 层的 GC 行为和引擎资源生命周期。通过减少堆分配、复用对象、及时释放资源,并结合 Profiler 分析,能有效优化内存使用,避免卡顿和泄漏。
更多教学视频