unity 垃圾回收机制

unity垃圾回收就是底层的内存管理机制
分为两个部分
1.GC部分
由C#创建的引用类型,分配在托管堆上,是由Mono 或者 IL2Cpp来管理
GC工作原理
用三色标记法,最先,所有的节点都是白色,从根节点出发(一般是静态对象,活动栈帧中的局部变量,CPU 寄存器,GC 句柄表。。后面这3个我都不知道是什么),这些根节点标记为灰色,在这些节点中找到他们引用到的对象,将他们标记为灰色,加入灰色列表,同时将拥有引用对象的对象标记为黑色。之后依次遍历灰色列表中存储的对象,找到里面引用到的其他对象,标记灰色。
遍历整个托管堆内存,将仍然还是白色的对象释放,加入空闲内存链表,灰色或者黑色的就重置为白色,等待下次检查。
GC工作步骤:
标记-清除-压缩(移动未被清除的对象,合并空闲内存,减少内存碎片)
GC触发时机:
1.自动触发:托管堆到达阈值,每帧会检查托管堆状况
2.手动触发: System.GC.Collect()
3.场景切换时可能会触发

GC可能会导致主线程卡顿,因为GC是运行在主线程中的,堆越大,对象越多,GC需要的时间越长,清除完成后,还会对碎片进行整理,移动对象到新的内存空间。
运行GC期间,STW(Stop-The-World):遍历期间暂停所有托管线程(保证对象图不变)
GC也会产生很多的堆碎片

常见GC问题来源:
高频实例化:每帧 new 对象(如 Vector3、RaycastHit[])。
字符串操作:频繁拼接字符串(string 不可变,生成垃圾),应该用stringbuilder。
装箱(Boxing):值类型转引用类型(如 int 存入 ArrayList)。
闭包与匿名方法:捕获局部变量时生成额外对象。

诊断GC的工具:
1.Unity Profiler
[file:///C:/Program%20Files/Unity/Hub/Editor/2022.3.57f1c2/Editor/Data/Documentation/en/Manual/ProfilerWindow.html]
2.Memory Profiler 包
3.GC 时间监控
void Update() { System.GC.Collect(); System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); // ... 代码块 sw.Stop(); Debug.Log($"GC Time: {sw.Elapsed.TotalMilliseconds} ms"); }

GC的优化:
1.减少内存分配(如上面常见来源的错误方式)
2.优化unity某些API调用
3.监控GC触发时机
例如可以手动触发,避免都在一个时间点释放
可以用工具来监测哪里的内存峰值,选择合适的时间点释放
4.使用增量式垃圾回收
在playersetting中开启增量式,让GC回收在多帧完成,减少单帧卡顿

2.非托管内存
Unity 引擎底层(如纹理、音效、Native 插件)使用非托管内存,需手动管理或通过引用计数释放

类型 示例 管理方式
Unity 引擎资源 纹理(Texture)、网格(Mesh)、音频(AudioClip)、Shader 等 引用计数 + 手动释放
Native 插件内存 C++ 插件、系统 API 分配的内存 手动管理(需实现释放逻辑)
第三方库内存 如 FMOD、物理引擎(PhysX)分配的内存 依赖库自身的释放机制
Graphics API 资源 GPU 缓冲区(VBO)、渲染纹理(RenderTexture) Unity 封装管理,但仍需注意泄漏
资源类型 释放方式
AssetBundle AssetBundle.Unload(true)(卸载资源)或 Unload(false)(保留实例化资源)
纹理/网格 Resources.UnloadUnusedAssets() + 确保无引用
RenderTexture RenderTexture.Release()
Native 插件 调用插件提供的 Release()/Dispose() 方法
对象实例 Destroy(gameObject)(移除场景对象)

GC为什么管不了这些?
因为GC是依靠C#的引用关系来管理对象,对这些资源,第三方库等等的无法监测,非托管的部分基本是unity底层(C++)或者外部类创建的,GC只会回收C#创建的包装器,但是底层资源并没有释放。

例子:
destory(gameObject)
Destroy(gameObject) 操作的对象同时涉及托管内存(C#层)和非托管内存(Unity引擎层),但它们的生命周期管理是分离的。
deepseek_mermaid_20250729_061fe0

1.托管内存(C#部分)

类型 内存位置 管理方式 示例
MonoBehaviour 托管堆 GC 管理 Enemy.cs 脚本实例
脚本中的引用类型字段 托管堆 GC 管理 public ParticleSystem ps;

2.非托管部分(Unity 引擎层)

类型 内存位置 管理方式 示例
场景节点数据 Native 堆 引擎引用计数 Transform 位置信息
网格/纹理数据 GPU 显存 引擎资源管理 Mesh 顶点数据
物理引擎数据 PhysX 私有内存 物理引擎管理 Collider 几何体

整个释放步骤
deepseek_mermaid_20250729_01e1fe

1.Destroy(gameObject) 主要处理非托管部分
立即:移除场景节点(非托管)
延迟:释放GPU/物理内存(非托管)

2.C# 对象由 GC 独立管理
即使Native资源释放,托管对象仍在堆中直到GC触发
但重载的 == null 会返回 true(引擎魔术)

3.泄露根源在于:
未通过Unity API释放Native资源,而非GC失效

创建一个gameobject的内存分配:
GameObject go = new GameObject("MyObj")
1.非托管部分:
C++底层,创建一个native object对象,有唯一标识符,状态,标签,层级等,有一个指向transform组件的指针。
如果这个对象有其他的组件,则也会有每个组件在非托管堆创建的native对象

2.托管部分 C#部分
有个托管对象,里面有个指针指向非托管部分的native 对象
挂载在这个对象上面的每个脚本实例

deepseek_mermaid_20250729_72d87a

只调用 Destroy(gameObject) 可能导致显存或物理内存泄漏。
因为这个对象可能还挂载着一些组件,例如mesh,collision ,这里会涉及到图集资源,物理数据等
这些资源如果是动态创建的,需要调用api来释放,但是要注意,有些资源是共享的,不能在某个使用它的对象销毁的时候也把这个资源释放,最好通过资源管理类来管理。

posted @ 2025-07-26 15:01  木土无心  阅读(93)  评论(0)    收藏  举报