【Unity小白学习日记3】吃透GC——C# 的垃圾回收器
2026/9/14 2:39:38 网站建设 项目流程

前言

1、GC 垃圾回收是什么?

2、GC的工作流程?

3、什么会产生 GC(重点,Unity 考点)

4、Unity 中减少 GC 的手段

5、GC 触发时机是什么?

6、【补充知识:内存泄漏】

常考问题:

Q:物体 Destroy 了,Mono 脚本一定被 GC 回收吗?(了解)

Q:协程为什么会造成内存泄漏?(了解)

首先先明确GC必须要学习的几个知识点:

1、GC 垃圾回收是什么?

2、GC的工作流程?

3、什么情况会产生 GC?

4、Unity 中减少 GC 的手段?

5、GC 触发时机是什么?

6、【补充知识:内存泄漏】

接下来我将一个一个来讲讲我的理解:

1、GC 垃圾回收是什么?

GC 全称 Garbage Collector,垃圾回收器。C# 是托管语言,不需要开发者手动释放堆内存,GC 自动扫描堆,回收没有任何引用指向的对象,释放堆内存。栈内存不受 GC 管理,函数执行完自动释放。

2、GC的工作流程?

工作流程:

  1. 标记阶段:从根对象出发扫描,所有能访问到、有引用的堆对象标记为存活。根对象包括静态变量、Mono 脚本、局部变量引用。
  2. 清除阶段:把没有标记、没有引用的对象判定为垃圾,回收内存。
  3. 压缩(整理)阶段:压缩的时候会 STW,暂停主线程,这就是 Unity 卡顿的来源。
STW:Stop‑The‑World,所有用户逻辑暂停,GC 工作的时候游戏会卡帧。

3、什么会产生 GC(重点,Unity 考点)

  1. new 引用类型对象 class、数组,在堆分配内存。
  2. 装箱拆箱:值类型转 object,堆上生成对象。
  3. 字符串频繁拼接,string 每次 + 生成新字符串。
  4. 闭包 lambda捕获局部变量,编译器生成隐藏类,堆分配。
  5. foreach 旧版本迭代器、协程,会生成迭代器对象。
  6. 集合扩容:List/Dictionary 容量不足,内部 new 新数组。

4、Unity 中减少 GC 的手段

  1. 尽量减少 Update 里面 new 引用对象,不要每帧 new。
  2. 优先泛型集合List<T>,避免非泛型集合带来装箱。
  3. 大量字符串拼接用StringBuilder。
  4. 对象池,复用物体、组件,减少频繁 Instantiate/Destroy,减少 new。
  5. 集合预先设置 Capacity,避免运行时扩容。
  6. 少在循环、Update 写 lambda 闭包捕获局部变量。
  7. 及时置空引用,解除静态、单例对废弃对象的持有。
  8. 可以手动调用GC.Collect()强制回收,但不建议频繁调用,会带来 STW 卡顿,一般只适合场景切换的时候调用一次。

5、GC 触发时机是什么?

GC 触发时机有三种:

  • 第一,new 新对象的时候,托管堆剩余内存不够分配,就自动触发回收
  • 第二,代码手动调用 GC.Collect 强制回收,游戏业务尽量不要手动调用
  • 第三,IL2CPP/Mono 内部达到内存阈值自动触发,不同平台阈值会不一样

6、【补充知识:内存泄漏】

内存泄漏就是程序中已经不再使用的对象,还被其他地方持有引用,GC 无法对它进行回收,这部分内存得不到释放。随着运行时间变长,内存持续上涨,会出现卡顿,严重会造成内存溢出、闪退。

Unity 里面最常见容易出现内存泄漏的几个原因:

第一,事件订阅忘记移除监听。【比如说弹窗 UI 在 OnEnable 订阅金币变化事件,关闭弹窗直接 Destroy 物体,没有在 OnDisable 移除监听。物体销毁,但事件中心字典仍然保存委托,不仅会后续广播时报空引用,Mono 对象也无法 GC 回收。】

第二,静态变量或者单例持有已经销毁的 Mono 对象。【比如单例管理器用静态变量保存玩家角色,场景切换角色被销毁,但静态变量没有置空,产生伪 null,对象得不到释放;Mono 单例 OnDestroy 忘记清空 Instance 也属于这种情况。】

第三,协程没有停止。【如果协程是别的物体启动的,目标物体被 Destroy 之后协程不会自动停止,协程迭代器会持有销毁物体的引用,造成泄漏;注意物体自身启动的协程,物体销毁会自动停止。】

第四,List、Dictionary 这类集合持续添加数据,没有做清理。【例如战斗伤害飘字不断加入 List,战斗结束只 Destroy UI 物体,没有 Clear 集合,集合一直持有对象引用,内存持续累积上涨。 】

开发规范:OnEnable 订阅事件,OnDisable 移除;物体销毁时停止协程;不要用静态变量缓存临时游戏物体;Profiler 的 Memory 面板可以用来排查内存泄漏。

常考问题:

Q:物体 Destroy 了,Mono 脚本一定被 GC 回收吗?(了解)

话术:

不一定。如果还有静态变量、事件委托、未停止的协程持有这个对象引用,C# 对象不会被 GC 回收,会出现伪 null。引擎层物体销毁,C# 对象还留在内存,就是内存泄漏。

Q:协程为什么会造成内存泄漏?(了解)

话术:

物体被 Destroy,如果没有 StopCoroutine,协程还会继续执行。协程的 IEnumerator 会持有 Mono 对象,造成对象无法 GC 回收,引发内存泄漏。物体销毁会自动停止该物体上所有协程,但如果协程是其他对象启动的就不会自动停止。

拓展知识:托管内存和非托管内存(先简单讲讲后续我会出详细的介绍)

托管内存由 .NET GC 管理;非托管内存或非托管资源不由 GC 直接管理,需要开发者显式释放。

重点注意:GC 负责回收托管对象的内存,但不等于所有资源都自动释放。涉及文件、网络、数据库、Unity Native 资源时,要实现或调用 IDisposable.Dispose() ,优先用 using ,必要时用 SafeHandle 管理非托管句柄。

小何同学:那些默默啃下的知识点,终会变成面试里从容回答的底气。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询