SerializeReference 内存泄漏之谜
栏目:Unity Bug挑战 | 期号:NO.010 | 日期:2026-10-30 | 4星 · 中高级 | Bug挑战 | Unity 2022.3 LTS · 序列化 / 内存
Bug现场
你接手了一个上线半年的卡牌手游项目,最近刚把引擎从 2021.3 LTS 升到了 2022.3.28f1。升级后 QA 反馈了一个诡异的问题:
游戏玩得越久,内存涨得越高,切场景也降不下来。 最夸张的测试是连续打 20 局战斗,内存从初始 800MB 涨到了 1.5GB,低端机直接 OOM 闪退。
你的第一反应是「是不是哪里资源没释放」,于是打开 Memory Profiler 开始排查——结果大跌眼镜:
- 托管堆(Managed Heap)很稳定,一直在 120MB 左右,没有增长
- Texture、Mesh、AudioClip 这些资源也都正常,切场景后该释放的都释放了
- 但是!Native Memory 下面有个 UnitySubsystemsObjects – 分类,一直在稳步上涨,每局战斗涨 30~50MB,切场景完全不回落
这就奇怪了。UnitySubsystemsObjects 是什么?为什么会有 的对象在泄漏?
你翻了一下代码,项目里大量使用了 [SerializeReference] 特性来做技能系统的多态序列化——大概有 200 多个不同的技能效果类,都挂在 SkillData 这个 ScriptableObject 上,通过 Addressables 动态加载和释放。
简化后的核心代码如下:
public abstract class SkillEffectBase
{
public abstract void Apply(GameObject target);
}
[Serializable]
public class DamageEffect : SkillEffectBase
{
public int damage;
public override void Apply(GameObject target) { /* … */ }
}
[Serializable]
public class HealEffect : SkillEffectBase
{
public int healAmount;
public override void Apply(GameObject target) { /* … */ }
}
[CreateAssetMenu(fileName = "SkillData", menuName = "Game/Skill Data")]
public class SkillData : ScriptableObject
{
public string skillName;
public int cooldown;
[SerializeReference]
public List<SkillEffectBase> effects = new List<SkillEffectBase>();
}
加载和释放的逻辑:
public class SkillLoader : MonoBehaviour
{
private AsyncOperationHandle<SkillData> _handle;
public async Task LoadSkill(string address)
{
_handle = Addressables.LoadAssetAsync<SkillData>(address);
await _handle.Task;
}
public void UnloadSkill()
{
if (_handle.IsValid())
{
Addressables.Release(_handle);
}
}
}
你做了一个最小测试:创建一个只有 1 个 DamageEffect 的 SkillData,反复 Load → Release → UnloadUnusedAssets → GC.Collect,发现 UnitySubsystemsObjects 每次都涨一点,从不回落。
诡异现象
项目 | 预期 | 实际
托管堆内存 | 加载后释放,回到初始水平 | 正常,符合预期
资源内存(Texture/Mesh等) | 释放 Addressable 后回落 | 正常,符合预期
Native-UnitySubsystemsObjects | 释放后应该清零 | 每次加载都增长,永不释放
调用 UnloadUnusedAssets | 回收所有未引用的 Native 对象 | 对这部分内存完全无效
更诡异的是:
- 同样的代码在 2021.3 LTS 上完全正常,内存可以回收
- 升级到 2022.3.20 之后的版本就开始泄漏
- 不只是 ScriptableObject,任何带有 [SerializeReference] 字段的 MonoBehaviour、普通类实例,只要被销毁就会泄漏
- Profiler 里看不到具体是什么对象在泄漏,统一显示
排查思路提示
方向一:SerializeReference 的底层实现机制
[SerializeReference] 是 Unity 2019.3 引入的特性,支持序列化多态引用。它和 [SerializeField] 的底层实现有什么不同?序列化时除了数据本身,还需要存储什么额外信息?这些额外信息存在哪里?
方向二:Unity 2022.3 的序列化重构
Unity 在 2022 版本对序列化系统做了不少改动,引入了 Unity Subsystems 相关的 Native 层管理。UnitySubsystemsObjects 这个分类在 2021 版本的 Profiler 里根本不存在。这个分类到底是管什么的?
方向三:引用计数 vs GC
Unity 的 Native 对象是引用计数管理的,而 C# 托管对象是 GC 管理的。两者之间的桥接出了问题,就会导致 Native 内存泄漏。SerializeReference 的多态对象,它们的 Native 侧表示是什么样的?引用计数是怎么维护的?
(答案区在下方,先思考再往下翻)
标签:Unity、Bug挑战、SerializeReference、内存泄漏、Native内存、UnitySubsystemsObjects、游戏开发、C#
网硕互联帮助中心




评论前必须登录!
注册