• .NET中常见的内存泄漏和解决办法


    在.NET中,虽然CLR的GC垃圾回收器帮我们自动回收托管堆对象,释放内存,最大程度避免了"内存泄漏"(应用程序所占用的内存没有得到及时释放),但.NET应用程序"内存泄漏"的问题还是会存在,如果不加以注意,"内存泄漏"时有发生。


    有关流以及Reader或Writer引起的内存泄漏

     

    比如,把文件读取到流中:

    public static string ReadFile()
    
    {
    
        var filePath = @"硬盘地址";
    
        var sr = new StreamReader(filePath);
    
        return sr.ReadToEnd();
    
    }

    以上,StreamReader在读取数据后没有解释销毁,存在"内存泄漏"。正确的做法是在使用完后及时关闭。

    public static string ReadFile()
    
    {
    
        var filePath = @"硬盘地址";
    
        using(var sr = new StreamReader(filePath))
    
        {
    
            return sr.ReadToEnd();
    
        }
    
    }

    或者

    public static string ReadFile()
    
    {
    
        var filePath = @"硬盘地址";
    
        var sr = new StreamReader(filePath);
    
        var result = sr.ReadToEnd();
    
        sr.Close();
    
        return result;
    
    }

    以上,需要我们注意的是:当通过某种流的构造函数创建的对象实例,注意及时关闭。

    有时候,通过某个方法返回某种流的对象实例,也会忘记关闭。比如以下:

    //创建字节数组
    
    var data = new byte[1024];
    
    var client = new TcpClient();
    
    //从TCP实例方法返回流
    
    var stream = client.GetStream();
    
    //把流读到字节数组中
    
    int bytesLength = stream.Read(data, 0, data.Length);
    
    //字节数组转换城字符串
    
    var result = System.Text.Encoding.ASCII.GetString(data, 0, bytesLength);
    

    正确的写法应该是:

    //创建字节数组
    
    var data = new byte[1024];
    
    var client = new TcpClient();
    
    //从TCP实例方法返回流
    
    var stream = client.GetStream();
    
    //把流读到字节数组中
    
    int bytesLength = stream.Read(data, 0, data.Length);
    
    stream.Close();
    
    //字节数组转换城字符串
    
    var result = System.Text.Encoding.ASCII.GetString(data, 0, bytesLength);
    

    同理,其它与流有关的类,我们也需要注意在用完后及时关闭:

    ● FileStream
    ● MemoryStream
    ● StreamReader
    ● TextWriter
    ......

    静态引用引起的内存泄漏

     

    对于静态实例来说,除非应用程序关闭,对应的内存一直得不到释放。比如有如下遵循"Siingleton"模式的类(没考虑线程安全)。

    public class MySingletonClass
    
    {
    
        private static MySingletonClass myInstance;
    
        private static List<IAmBig> bigObjects = new List<IAmBig>();
    
        private MySingletonClass(){}
    
        public static MySingletonClass MyInstance
    
        {
    
            get
    
            {
    
                if(myInstance == null)
    
                {
    
                    myInstance = new MySingletonClass();
    
                }
    
                return myInstance;
    
            }
    
        }
    
        public static IAmBig CreateBigObject()
    
        {
    
            var bigObject = new IAmBig();
    
            bigobject.AllocateMemory(4096);
    
            bigObjects.add(bigObject);
    
            return bigObject;
    
        }
    
    }
    
    public class IAmBig
    
    {
    
        
    
    }
    

    以上,每次调用CreateBigObject静态方法,都往List<IAmBig>类型集合中添加,由于MySingletonClass静态类实例一直存在于应用程序的生命周期,再加上GC不会释放bigObjects这个集合对象实例,于是就出现了"内存泄漏"。解决办法是避免让静态实例引用其它实例对象,避免出现静态实例的链式引用。

    委托引起的内存泄漏

     

    比如有2个委托形成的委托链。

    var objectOne = new ObjectOne();
    
    var objectTwo = new ObjectTwo();
    
    objectOne.StateChanged += objectTwo.StateChangedEventHandler;
    
    objectTwo.Dispose();
    

    以上,把objectTwo的委托注册给了objectOne,这样objectOne和objectTwo有依赖关系,形成了依赖链。只有当objectOne被释放,才能释放objectTwo。如果objectOne恰巧是全局静态实例,那在应用程序的生命周期内,objectTwo一直得不到内存释放,造成了"内存泄漏"。

    解决办法是在调用objectTwo的Dispose方法之前,先解开两者的依赖关系。修改如下:

    var objectOne = new ObjectOne();
    
    var objectTwo = new ObjectTwo();
    
    objectOne.StateChanged += objectTwo.StateChangedEventHandler;
    
    ......
    
    objectOne.StateChanged -= objectTwo.StateChangedEventHandler;
    
    objectTwo.Dispose();
    

    非托管资源引起的内存泄漏

     

    public class MyUnManagedExample
    
    {
    
        public void Allocate()
    
        {
    
            IntPtr pointer = Marshal.AllocHGlobal(1024);
    
        }
    
    }

    对于创建的非托管类型的实例ponter,需要显式释放。

    Marshal.FreeGlobal(pointer);

    实现IDisposable接口的类引起的内存泄漏

     

    所有实现IDisposable接口的类都有一个Dispose方法,如果忘记调用,就造成"内存泄漏"。

  • 相关阅读:
    知识点总结(基础篇)
    从FBV到CBV三(权限)
    从FBV到CBV二(认证器)
    kubeDNS workflow(service registration and discovery)
    从FBV到CBV一(开始)
    pycharm的python console报错CE.app/Contents/helpers/pydev/_pydev_bundle/pydev_ipython_console_011.py", line 87, in init self.matchers.remove(self.python_matches) ValueError: list.remove(x): x not in list
    删除none 的images报错 image has dependent child images 解决办法
    缓存的应用场景以及要注意的问题
    epoll—IO多路复用
    docker 容器启动后立马退出的解决方法
  • 原文地址:https://www.cnblogs.com/darrenji/p/4650822.html
Copyright © 2020-2023  润新知