一、内存泄漏的那些事儿

咱们程序员最怕的就是程序跑着跑着突然变卡,最后直接崩溃。很多时候,这都是内存泄漏惹的祸。简单来说,内存泄漏就是程序申请了内存却没释放,就像你租了房子却忘了退租,房东(系统)还以为房子一直有人住呢!

在DotNetCore里,虽然有了GC(垃圾回收)这个“保洁阿姨”,但如果你乱扔垃圾(比如静态集合一直追加数据),阿姨也会忙不过来。下面咱们就聊聊怎么抓出这些“垃圾制造者”。

二、如何发现内存泄漏

1. 基础工具:Visual Studio诊断工具

如果你用Visual Studio开发,可以直接用内置的诊断工具:

  1. 启动应用
  2. 点击“调试” → “性能探查器” → 勾选“.NET对象分配”和“.NET内存使用情况”
  3. 操作你的应用,比如反复调用某个接口
  4. 分析报告中哪些对象在“野蛮生长”

2. 进阶工具:dotnet-dump

生产环境没有VS?别慌,用dotnet-dump抓个内存快照:

# 安装分析工具
dotnet tool install -g dotnet-dump
# 抓取进程内存(假设进程ID是1234)
dotnet-dump collect -p 1234
# 生成分析报告
dotnet-dump analyze dump_20230801.dmp
> dumpheap -stat  # 查看对象统计

比如发现List<string>占了500MB,那就要重点检查了。

三、典型泄漏场景与修复

1. 静态集合的坑

// 错误示例:静态集合会一直增长
public static class CacheService
{
    private static readonly List<string> _cache = new List<string>();
    
    public static void AddData(string data)
    {
        _cache.Add(data); // 数据只进不出!
    }
}

// 正确做法:限制大小或定期清理
public static class SafeCache
{
    private static readonly ConcurrentQueue<string> _cache = new ConcurrentQueue<string>();
    private static readonly int MaxSize = 1000;

    public static void AddData(string data)
    {
        _cache.Enqueue(data);
        while (_cache.Count > MaxSize) _cache.TryDequeue(out _);
    }
}

2. 事件未注销

// 错误示例:事件订阅导致对象无法释放
public class EventPublisher
{
    public event Action OnEvent;
}

public class EventSubscriber
{
    public void Subscribe(EventPublisher publisher)
    {
        publisher.OnEvent += HandleEvent; // 订阅后未取消
    }

    private void HandleEvent() { }
}

// 正确做法:实现IDisposable
public class SafeSubscriber : IDisposable
{
    private EventPublisher _publisher;

    public SafeSubscriber(EventPublisher publisher)
    {
        _publisher = publisher;
        _publisher.OnEvent += HandleEvent;
    }

    public void Dispose()
    {
        _publisher.OnEvent -= HandleEvent; // 显式取消订阅
    }
}

四、高级排查技巧

1. 使用WeakReference检测对象存活

// 测试对象是否被意外持有
var obj = new ExpensiveObject();
var weakRef = new WeakReference(obj);

// 模拟GC
GC.Collect();
GC.WaitForPendingFinalizers();

if (!weakRef.IsAlive) 
{
    Console.WriteLine("对象已正确释放");
}

2. 分析内存碎片

有时候泄漏不明显,但内存碎片化严重:

# 使用dotnet-counters查看GC情况
dotnet-counters monitor --process-id 1234 System.Runtime
# 关注"GC Heap Size"和"Gen 2 Collections"指标

五、预防性编程建议

  1. 定期代码审查:重点检查静态字段、事件、缓存
  2. 自动化测试:用内存分析工具集成到CI/CD流水线
  3. 监控生产环境:配置Prometheus+Grafana监控GC指标

六、总结

内存泄漏就像程序里的慢性病,初期没感觉,等发作时就晚了。好在DotNetCore提供了丰富的工具链,从开发时的Visual Studio诊断工具,到生产环境的dotnet-dump,配合良好的编程习惯(比如及时释放资源、避免滥用静态对象),基本可以做到早发现早治疗。

最后送大家一句话:对待内存要像对待钱包一样——该花的时候花,该省的时候省,千万别漏!