『内存泄露』.NET中常见的内存泄露场景及分类-LMLPHP
📣读完这篇文章里你能收获到

  • 托管内存跟非托管内存泄露的常见场景总结
  • 了解到常见的非托管资源有哪些
  • 非托管资源使用的最佳实践

『内存泄露』.NET中常见的内存泄露场景及分类-LMLPHP

『内存泄露』.NET中常见的内存泄露场景及分类-LMLPHP

一、托管内存泄露

1. 静态变量

静态变量和它们引用的任何东西都不会被垃圾收集

public class MyClass
{
    static List<MyClass> _instances = new List<MyClass>();
    public MyClass()
    {
        _instances.Add(this);
    }
}

2. 本地缓存

本地缓存使用时,如果不加以限制,会导致内存泄露并抛出OutOfMemoryException异常,使用时需注意:

  1. 删除一段时间未使用的缓存
  2. 限制缓存大小

3. 订阅事件

一旦你订阅了一个事件,这个对象就拥有了对你的类的引用

public class MyClass
{
    public MyClass(WiFiManager wiFiManager)
    {
        wiFiManager.WiFiSignalChanged += OnWiFiChanged;
    }

    private void OnWiFiChanged(object sender, WifiEventArgs e)
    {
        // do something
    }
}

4. 在匿名方法中捕获成员

public class MyClass
{
    private JobQueue _jobQueue;
    private int _id;

    public MyClass(JobQueue jobQueue)
    {
        _jobQueue = jobQueue;
    }

    public void Foo()
    {
        _jobQueue.EnqueueJob(() =>
        {
            Logger.Log($"Executing job with ID {_id}");
            // do stuff 
        });
    }
}

在这段代码中,成员_id是捕获在匿名方法中,因此实例也被引用。这意味着虽然JobQueue存在并引用该作业委托,它还将引用MyClass.

解决方案非常简单——分配一个局部变量:

public class MyClass
{
    public MyClass(JobQueue jobQueue)
    {
        _jobQueue = jobQueue;
    }
    private JobQueue _jobQueue;
    private int _id;

    public void Foo()
    {
        var localId = _id;
        _jobQueue.EnqueueJob(() =>
                             {
            Logger.Log($"Executing job with ID {localId}");
            // do stuff 
        });
    }
}

二、非托管内存泄露

1. 常见的非托管资源

常见的非托管资源有:文件句柄、数据流、数据库连接、网络连接

2. 使用Dispose模式

定义封装非托管资源的对象时,建议在公共 Dispose 方法中提供必要的代码以清理非托管资源。 通过提供 Dispose 方法,对象的用户可以在使用完对象后显式释放资源。 使用封装非托管资源的对象时,务必要在需要时调用 Dispose。在使用时加上using语句

3. 最佳实践

3.1 读取文件流

public void Foo()
{
    //使用
    using (var stream = new FileStream(@"C:\Temp\SomeFile.txt",FileMode.OpenOrCreate))
    {
        // do stuff
    }
    // stream.Dispose() 即使发生异常也会被调用
}

3.2 自行分配非托管资源

添加析构函数,对象回收时如果没调用dispose方法,非托管对象也可以正常释放

public class MyClass : IDisposable
{
    private IntPtr _bufferPtr;
    public int BUFFER_SIZE = 1024 * 1024; // 1 MB
    private bool _disposed = false;

    public MyClass()
    {
        _bufferPtr =  Marshal.AllocHGlobal(BUFFER_SIZE);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            // 释放托管资源
        }

        // 释放非托管资源
        Marshal.FreeHGlobal(_bufferPtr);
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
    	//确保在垃圾回收时不会调用析构函数
        GC.SuppressFinalize(this);
    }

    ~MyClass()
    {
        Dispose(false);
    }
}
04-05 18:24