UE4.21 MaterialShaderMap 内存泄漏排查与修复
UE4.21 MaterialShaderMap 内存泄漏排查与修复
一个 UE4.21 项目里,用 Low Level Memory Tracker(LLM)观察内存时发现 material shader map 这个 tag 的内存越涨越高,切图不释放。走完所有关卡(含 base、dlc1、dlc2)后,没释放的内存累积到 200MB 量级。这篇记一下怎么定位到根因,以及补丁怎么打。
现象:切图不释放
用 LLM 追踪 material shader map,进出不同关卡时内存单调增长——正常情况下卸载关卡应该把对应的 shader map 内存还回去,但这里没有。这是典型的「卸载时漏了释放」。
根因:shader resource 复用时漏了 shader code 的引用释放
问题在加载 shader resource 时的复用逻辑。UE 在反序列化时会加载 shader resource,如果内存里已经存在一个匹配的 resource,就复用已有的、把新反序列化出来的那个删掉。
关键点是:反序列化那个 resource 时会同时加载 shader code。如果直接 delete 掉这个临时 resource,却不先释放它持有的 shader code,那么 shader code 的引用计数就永远减不到 0,对应内存也就永远卸载不掉。
// Reuse an existing shader resource if a matching one already exists in memory
if (ExistingResource)
{
// 因为 resource 在序列化时加载了 shader code,
// 直接 delete 它之前必须先 Release,否则 shader code 引用计数
// 减不到 0,永远不会被卸载
SerializedResource->Release();
delete SerializedResource;
SerializedResource = ExistingResource;
}
else
{
// Register the newly loaded shader resource so it can be reused by other shaders
SerializedResource->Register();
}
补上那一行 SerializedResource->Release() 就是全部修复。
修复效果
同样的进出关卡序列,修复前后对比(单位 MB):
| 关卡序列 | 修复前 | 修复后 |
|---|---|---|
| 主菜单 | 42.24 | 42.03 |
| 进入室外关 A | 147.9 | 143.03 |
| 进入关卡 B | 207.59 | 177.56 |
| 再次进入室外关 A | 185.2 | 143.86 |
重点看最后一行:修复前再次进入同一张图内存停在 185MB(比第一次进还高,说明累积了泄漏),修复后回落到 143.86MB,和第一次进入基本持平——说明卸载时内存正确还回去了。
一条经验
这类「切图内存不释放」的坑,delete 一个持有子资源的对象前,要先确认它持有的引用计数资源有没有被正确 Release。序列化路径尤其容易漏——因为对象是在反序列化时「顺手」加载了子资源,写复用/丢弃逻辑的人往往只看到了外层对象,没意识到里面还挂着一份 shader code 的引用。