《Unreal 对 C++ 做了什么》系列 10. 垃圾回收器 (GC) 原理:标记-清除与 GC 根对象
2026/4/21 5:28:04 网站建设 项目流程

《Unreal 对 C++ 做了什么》系列 (10/54)

10. 垃圾回收器 (GC) 原理:标记-清除与 GC 根对象 🧹

🚀 导言:为什么 C++ 需要垃圾回收?

在传统的 C++ 开发中,“谁申请,谁释放”是金科玉律。但在拥有数万个相互引用对象的游戏引擎中,手动管理引用链几乎是不可能的。如果 Actor A 引用了数据 B,而 B 又引用了特效 C,手动删除 A 时,你必须确保 B 和 C 没有被其他地方引用。

虚幻引擎通过一套名为Mark and Sweep(标记-清除)的垃圾回收机制,解决了这个难题。


🔑 1. 核心算法:标记-清除 (Mark and Sweep)

UE 的 GC 并不是实时的(不像 C# 的分代回收),而是一种追踪式的回收。它分为两个主要阶段:

阶段 A:标记 (Marking)

GC 会从一组被称为Root Set(根集)的对象开始,顺着它们身上的UPROPERTY指针向下摸索。

  • 只要是能从“根”顺着指针摸到的对象,就被标记为“可达(Reachable)”。
  • 没被摸到的对象,就像是断开连接的孤岛,被标记为“不可达”。
阶段 B:清除 (Sweeping)

引擎遍历全局对象表(GUObjectArray),将所有没有“可达”标记的对象彻底从内存中抹除,并归还内存。


🔑 2. 什么是“根” (Root Set)?

如果所有对象都被 GC,那谁来保护第一批对象不被回收?答案就是Root Set
以下对象会自动进入根集,成为 GC 扫描的起点:

  • UGameEngine:引擎对象。
  • UWorld:当前的关卡及其所有的 Actor。
  • UGameInstance:贯穿游戏始终的全局对象。
  • 被标记为Root的对象:你可以通过AddToRoot()手动将一个对象固定在内存中(记得用RemoveFromRoot()释放,否则会内存泄漏)。

🔑 3. 为什么UPROPERTY如此重要?

这是初学者最容易崩溃的地方:GC 只认UPROPERTY

UCLASS()classAMyCharacter:publicACharacter{GENERATED_BODY()// 情况 1:GC 知道 Character 引用了 WeaponUPROPERTY()UWeapon*MyWeapon;// 情况 2:GC 彻底无视这个指针!UWeapon*HiddenWeapon;};
  • 情况 1:当 GC 扫描 Character 时,发现它有一个UPROPERTY指向MyWeapon。于是MyWeapon被标记为可达,幸免于难。
  • 情况 2:虽然你在 C++ 里赋值了HiddenWeapon,但 GC 扫描时看不见它。GC 会认为HiddenWeapon指向的对象没有人用,直接将其回收。此时HiddenWeapon就成了一个野指针,一旦访问立即崩溃。

🔑 4. 性能克星:GC 引起的卡顿 (Hitch)

GC 扫描几万个对象是需要时间的。在早期版本中,GC 会“冻结”整个游戏线程来执行扫描,这就是为什么老游戏有时会突然卡一下。

UE 对 C++ 做的优化:

  1. 并行标记 (Parallel Mark):利用多线程同时扫描不同的对象树。
  2. 增量清除 (Incremental Reachability Analysis):将扫描工作拆分到多帧完成,避免单帧耗时过长。
  3. 簇 (Clustering):将一些永远会在一起的对象(如某个 Actor 及其所有 Component)打包成一个“簇”。GC 只需要检查 Actor 是否可达,就可以直接判定整个簇的状态,极大提升速度。

📊 GC 运转流程表

步骤执行内容开发者职责
1. 注册对象创建时进入GUObjectArray使用NewObjectSpawnActor
2. 追踪GC 扫描UPROPERTY引用链务必给 UObject 指针加UPROPERTY()
3. 标记判定对象是否可达正常引用即可,特殊情况使用AddToRoot
4. 销毁释放不可达对象的内存确保没有非UPROPERTY指针指向它

⚠️ 避坑指南:如何与 GC 和谐共处?

  1. 容器保护:如果你用TArray<UObject*>存对象,这个数组也必须加UPROPERTY(),否则数组里的对象会被当成垃圾清理掉。
  2. **避免手动delete**:永远不要对UObject使用delete。如果你想让它消失,取消对它的引用,或者调用MarkAsGarbage()(UE5)。
  3. 静态变量风险:普通的static UObject* MyGlobalPtr无法被UPROPERTY标记。如果必须使用全局 UObject,请考虑将其放入UGameInstance中管理。

结语

垃圾回收是虚幻引擎为 C++ 开发者提供的“安全气囊”。它通过UPROPERTY引用链建立了一套自动化的生存法则。只要你遵循“凡是 UObject 指针必加宏”的原则,你就已经掌握了 UE 内存管理的一半真谛。


下一篇预告:《11. 弱引用与软引用:TWeakObjectPtr 和 TSoftObjectPtr》。我们将探讨:如果我不希望一个指针“保住”对象的命,或者我希望延迟加载对象,该怎么办?

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

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

立即咨询