UE 的内存问题分两类:该死不死(泄漏)和不该死却死了(野引用)。两类问题的排查工具是同一套——obj 系列控制台命令。这些命令直接操作 GUObjectArray 和 GC 的引用图,是定位 UObject 问题的第一手段。下面基于 UE5.3 把常用命令和实战套路整理出来。
obj list:看对象分布
obj list 按类统计对象数量和内存占用,输出到日志。常用变体:
obj list # 所有类,按数量排序
obj list class=AActor # 只看 AActor 及其子类
obj list -alphasort # 按类名字母序(方便对比两次快照)
obj list -mincount=100 # 只显示数量超过 100 的类
输出格式:
Class Count MemoryKB
AMyEnemyCharacter 1234 5678
UMyWidgetComponent 5678 2345
...
排查泄漏的第一步是对比快照:在游戏运行到状态 A 时 obj list -alphasort > A.log,运行到状态 B 时再 > B.log,diff 两份日志找数量异常增长的类。比如发现 AMyProjectile 从 50 涨到 5000,说明发射物没回收。
obj refs:追踪引用链
找到泄漏的类之后,用 obj refs 查谁还引用着它:
obj refs name=AMyProjectile_123 # 查指定对象的所有引用
obj refs name=AMyProjectile_123 shortest # 只显示到 root 的最短路径
输出是一棵引用树,从目标对象向上追溯到 root:
AMyProjectile_123
<- UMyGameMode::ActiveProjectiles[47]
<- UMyGameMode (root)
这条链说明:Projectile 被 GameMode 的 ActiveProjectiles 数组持有,而 GameMode 是 root 对象——所以 Projectile 永远不会被 GC。修复就是在 Projectile 失效时从数组移除。
shortest 参数特别有用:引用链可能有很多条,shortest 只显示到 root 的最短一条,通常是"罪魁祸首"。
obj gc:强制 GC 并看结果
obj gc 立即触发一轮完整 GC(忽略帧预算),输出回收统计:
obj gc # 触发 GC
obj gc incremental=0 # 强制非增量(一次性全量标记)
配合 obj list 用:obj list 记录快照 → obj gc → 再 obj list。如果 GC 后某类对象数量没降,说明有引用链连着它——转 obj refs 排查。
obj dump:查看对象属性
obj dump name=X 把对象的每个 UPROPERTY 当前值打出来,走 FProperty 链逐个读:
obj dump name=AMyActor_123
输出类似:
Health: 100
MaxHealth: 150
bIsActive: true
TargetEnemy: AMyEnemy_456
...
这个命令的价值在于不依赖 C++ 反射之外的任何信息——引擎纯靠 FProperty 的偏移量读内存,所以即使对象处于异常状态(比如 BeginDestroy 之后),只要内存没释放就能 dump 出来。
实战案例 1:UI 泄漏
症状:每次打开关闭背包界面,内存涨 2MB。
排查:
obj list class=UUserWidget发现UBagWidget数量只增不减。obj refs name=UBagWidget_47 shortest显示被UMyHUD::CachedWidgets持有。- 查代码发现关闭时只调了
RemoveFromParent,没从CachedWidgets数组移除。
修复:关闭时同步清理缓存数组。
实战案例 2:野指针崩溃
症状:随机崩溃,调用栈显示访问 AMyEnemy 的 Health 属性时违例。
排查:
- 崩溃日志里对象地址是
0xdeadbeef模式(UE 的已释放内存填充值)。 obj list class=AMyEnemy确认该对象已被 GC。- 查引用方代码,发现用裸
AMyEnemy*缓存了敌人指针,没加UPROPERTY——GC 不知道这个引用,敌人死亡后被回收。
修复:缓存改用 TWeakObjectPtr<AMyEnemy>,或加 UPROPERTY 让 GC 感知。
辅助命令
memreport -full:生成完整内存报告,包含 UObject 统计、按 LLM 标签的内存分布。stat memory:实时显示内存总量和各子系统占用。stat uobjects:显示 UObject 数量、GC 时间。gccollect:等价于obj gc的简写。
实战结论
- 泄漏排查三板斧:
obj list对比快照 →obj refs shortest找引用链 → 修代码断链。 - 野引用排查优先查裸指针缓存——
UPROPERTY漏标是最常见根因。 obj gc之后数量不降 = 有强引用;降了但慢 = 正常,GC 是增量的。- 编辑器下
obj命令的输出量比 standalone 大很多(编辑器对象多),建议在 standalone 或打包版本里排查。