You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor项目中死对象内存未回收问题原因及解决方法咨询

Blazor内存持续增长问题分析与解决

问题复现代码

页面调用代码:

<button @onclick='(() => TestMemory())'>Insert Memory</button>

<h3>Times function is run: @counter</h3>

@code {
    int counter = 0;

    public void TestMemory()
    {
        _agp.TestClas();
        counter++;
    }    
}

被调用的函数:

public void TestClas()
{
    List<TestClass> testList = new List<TestClass>();

    for (int i = 0; i < 100000; i++)
    {
        testList.Add(new TestClass() { ArtId = i });
    }
}

死对象迟迟不回收的原因

  1. .NET GC的回收机制特性:.NET垃圾回收是分代、按需触发的,并非对象一脱离作用域就立刻清理。小对象会先存放在Gen0堆中,只有当Gen0堆占满、内存压力达到阈值时才会触发回收;如果系统内存充足,GC会延迟执行,导致“死对象”暂时驻留内存。
  2. Blazor框架的隐式引用持有:Blazor组件实例、事件回调(比如按钮的@onclick)可能被框架内部的状态管理机制间接持有,导致TestClass对象的引用链没有彻底断开,GC无法判定其为可回收对象。
  3. List动态扩容产生内存碎片:List默认动态扩容,每次扩容会生成新的内部数组,旧数组若未被GC及时回收,会形成内存碎片,直观表现为内存持续增长。你尝试的Clear、设null、TrimExcess只能处理List本身的内容或容量,无法强制GC回收这些旧数组。

解决办法

  1. 测试时强制触发GC:如果只是验证是否为GC延迟问题,可在TestMemory方法末尾添加强制回收代码(生产环境不建议频繁调用,会影响性能):
public void TestMemory()
{
    _agp.TestClas();
    counter++;
    // 强制触发全量GC,等待终结器执行后再回收一次
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
}

执行后观察内存是否下降,若下降则说明是GC延迟导致,而非真正的内存泄漏。

  1. 优化List初始化逻辑:创建List时直接指定容量,避免动态扩容产生的内存碎片:
public void TestClas()
{
    // 提前指定容量,减少内部数组扩容次数
    List<TestClass> testList = new List<TestClass>(100000);

    for (int i = 0; i < 100000; i++)
    {
        testList.Add(new TestClass() { ArtId = i });
    }
}
  1. 检查全局/单例对象的引用持有:确认_agp是否为全局单例或被长期持有?如果_agp的实例被静态对象、DI容器的单例服务持有,其方法中的对象可能被间接引用,导致无法回收。可尝试将_agp改为Scoped生命周期(业务场景允许的话)。

  2. 给TestClass实现IDisposable(按需):如果TestClass包含非托管资源或需要主动清理的内容,实现IDisposable接口并及时释放:

public class TestClass : IDisposable
{
    public int ArtId { get; set; }
    private bool _disposed = false;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        // 此处清理非托管资源
        _disposed = true;
    }
}

然后在TestClas中处理对象释放:

public void TestClas()
{
    List<TestClass> testList = new List<TestClass>(100000);

    for (int i = 0; i < 100000; i++)
    {
        var testObj = new TestClass() { ArtId = i };
        testList.Add(testObj);
    }
    // 遍历释放所有对象
    foreach (var item in testList)
    {
        item.Dispose();
    }
}
  1. 检查Blazor组件生命周期:如果组件被多次渲染但未正确销毁(比如未使用@key标识组件),旧组件实例会被保留,引发内存问题。确保组件在不需要时能被框架正确销毁。

内容的提问来源于stack exchange,提问作者Bram

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 01:25:23