当前位置:   article > 正文

C# 简述可能造成内存泄漏和内存溢出的几种情况_c# 内存溢出

c# 内存溢出

概念

内存溢出:通俗理解就是内存不够,系统中存在无法回收的内存或程序运行要用到的内存大于能提供的最大内存,从而造成“Out of memory”之类的错误,使程序不能正常运行。

内存泄露:内存泄漏指由于疏忽或错误造成程序不能释放或不能及时释放已经不再使用的内存的情况,是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存不能回收和不能及时回收,最后可能导致程序运行缓慢或者崩溃的问题。

造成内存泄漏的可能原因:
1、你的对象仍被引用但实际上却未被使用。 由于它们被引用,因此GC将不会收集它们,这样它们将永久保存并占用内存。
2、当你以某种方式分配非托管内存(没有垃圾回收)并且不释放它们。

实例

1.使用event造成的内存泄漏

class TestClassHasEvent
    {
        public delegate void TestEventHandler(object sender, EventArgs e);
        public event TestEventHandler YourEvent;
        protected void _Event(EventArgs e)
        {
            YourEvent?.Invoke(this, e);
        }
    }
     class TestListener
    {
 
        private TestClassHasEvent test;

        public TestListener(TestClassHasEvent _test)
        {
            test = _test;
            test.YourEvent += new TestClassHasEvent.TestEventHandler(_Event);
        }

        void _Event(object sender, EventArgs e)
        {

        }
        public TestListener()
        {
            SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);//静态
        }

        void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
        {

        }
    }
    class Program
    {
        static void DisplayMemory()
        {
            Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
        }

        static void Main()
        {
            DisplayMemory();
            Console.WriteLine();
            //TestClassHasEvent hasevent = new TestClassHasEvent();
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("--- New Listener #{0} ---", i + 1);
                //var listener = new TestListener(hasevent);
                var listener = new TestListener(new TestClassHasEvent());
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                DisplayMemory();

            }
            Console.Read();
        }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

在这里插入图片描述

static void Main()
        {
            DisplayMemory();
            Console.WriteLine();
            TestClassHasEvent hasevent = new TestClassHasEvent();
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("--- New Listener #{0} ---", i + 1);
                var listener = new TestListener(hasevent);           
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                DisplayMemory();

            }
            Console.Read();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在这里插入图片描述

上面的结果可以看出来,虽然进行了GC,但是实际上内存还是回慢慢的增大,这是因为hasevent实例一直存在,由于事件订阅了TestListener内部的方法,因此TestListener实例也不会进行释放。


     class TestListener
    {
        public TestListener()
        {
            SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);//静态
        }

        void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
        {

        }
    }
 static void Main()
        {
            DisplayMemory();
            Console.WriteLine();
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("--- New Listener #{0} ---", i + 1);
                var listener = new TestListener();
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
                DisplayMemory();

            }
            Console.Read();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

在这里插入图片描述
订阅了静态event的实例也不会进行释放。

2、静态变量

静态变量中的成员所占的内存不果不手动处理是不会释放内存的,单态模式的对象也是静态的,所以需要特别注意。因为静态对象中的成员所占的内存不会释放,如果此成员是以个对象,同时此对象中的成员所占的内存也不会释放,以此类推,如果此对象很复杂,而且是静态的就很容易造成内存泄露。

3、WPF绑定
WPF绑定实际上可能会导致内存泄漏。 一般我们都是绑定到DependencyObject或INotifyPropertyChanged对象。 下面的讲的例子,WPF将创建从静态变量到绑定源(即ViewModel)的强引用,从而导致内存泄漏。

这个View Model将永远留在内存中:

public class MyViewModel
{
    public string _someText = "memory leak";
    public string SomeText
    {
        get { return _someText; }
        set
        {
            _someText = value;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

而这个View Model不会导致内存泄漏:

public class MyViewModel : INotifyPropertyChanged
{
    public string _someText = "not a memory leak";
 
    public string SomeText
    {
        get { return _someText; }
        set
        {
            _someText = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof (SomeText)));
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

该类是从INotifyPropertyChanged派生的,这样会告诉wpf创建是的一个弱引用。

4、线程/计时器管理不当
创建一个具有对对象引用的线程,在不用的时候没有停止线程/计时器,那么这也会导致内存泄漏。

5、非托管资源

因为非托管资源所占的内存不能自动回收,所以使用后必须手动回收,否则程序运行多次很容易造成内存泄露

public class SomeClass : IDisposable
{
    private IntPtr _buffer;
 
    public SomeClass()
    {
        _buffer = Marshal.AllocHGlobal(1000);
    }
 
 	//及时释放
    public void Dispose()
    {
        Marshal.FreeHGlobal(_buffer);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

6、Dispose方法没被调用,或Dispose方法没有处理对象的释放。这样也会造成内存泄露
使用using可以避免产生这种问题

 using (MemoryStream stream = new MemoryStream()) 
{
    // ... 
}
  • 1
  • 2
  • 3
  • 4

在类中编写一个Dispose()方法统一释放所有对象,在析构函数中调用Disopose()。

   ~MyClass()
    {
        Dispose();
    }
  • 1
  • 2
  • 3
  • 4

内存溢出

如果在针对图片很大的情况下,或者频繁的调用体积很大的图片,直接引用地址,很可能就会造成内存溢出的问题。

ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(new Uri("bg.jpg", UriKind.Relative));
button.Background = imageBrush;
  • 1
  • 2
  • 3

其中imageBrush.ImageSource的类型为ImageSource,而ImageSource是个抽象类,因此我们不能直接使用它,而是使用它的子类来代替。

下面介绍一种方法:先看代码:

System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap("bg.jpg");
MemoryStream stream = new MemoryStream();
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
ImageBrush imageBrush = new ImageBrush();
ImageSourceConverter imageSourceConverter = new ImageSourceConverter();
imageBrush.ImageSource = (ImageSource)imageSourceConverter.ConvertFrom(stream);
button.Background = imageBrush;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其中bitmap即是存在于内存中的Bitmap类型图片,此处使用直接加载本地图片文件模拟。步骤是先将它保存到流中,再使用ImageSourceConverter 类的ConvertFrom方法从流中得到我们需要的图片。这样的方式可能很好的减缓内存的占用,当然如果是特别大的图片,可能这个方式也不能很好的解决,这个时候就可以考虑对图片的进行压缩后再处理,如果是同一个图片,可以使用静态对象保存再引用。

2、读写文件造成的内存溢出。
在固定缓冲区中写入文件的时候,必须检查一下缓冲区空间是否足够,并且及时清理缓冲区,避免出现内存溢出。
在这里插入图片描述

3、短时间内避免重复创建大量的耗内存对象。
我做了一个实验,每隔3s,创建一个对象。由下面的图可以看出,虽然一直进行GC(黄色部分代表进行垃圾回收),但是内存还是一直上涨,当到达一定值的时候就会内存溢出,造成崩溃。
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/115378
推荐阅读
相关标签
  

闽ICP备14008679号