【Unity Bug挑战】第10期 SerializeReference 内存泄漏之谜

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#

相关推荐
CSharp精选营21 分钟前
从控制台到「会动手」的 AI:用 Semantic Kernel 搭应用(小白教程)
c#·.net·ai agent·semantickernel·functioncalling
unity工具人18 小时前
unity 照片墙插件(Pop-up Photo Wall)在触摸屏和红外框设备上的触发Bug
bug
宝桥南山1 天前
.NET 11 - 尝试一下Blazor中的新特性SupplyParameterFromTempData
microsoft·微软·c#·asp.net·.net·.netcore
1-1=01 天前
C# 中 Lambda、Linq 与 Expression
c#·linq
zhanghaha13141 天前
C# 系列教程_7 判断语句(分支)保姆级精讲
开发语言·c#
zhanghaha13141 天前
C# 系列教程_8 循环语句 保姆级精讲(面向新手)
开发语言·c#
rockey6271 天前
AScript之LINQ扩展
c#·.net·script·动态脚本
1-1=02 天前
C# 委托与事件
开发语言·c#
tealcwu2 天前
【Unity基础】为什么游戏需要物理按键映射?从 Unity Input Manager 到跨引擎输入设计
java·游戏·unity