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();
        }
    }

在这里插入图片描述

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();
        }

在这里插入图片描述

上面的结果可以看出来,虽然进行了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();
        }

在这里插入图片描述
订阅了静态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;
        }
    }
}

而这个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)));
        }
    }

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

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

5、非托管资源

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

public class SomeClass : IDisposable
{
    private IntPtr _buffer;
 
    public SomeClass()
    {
        _buffer = Marshal.AllocHGlobal(1000);
    }
 
 	//及时释放
    public void Dispose()
    {
        Marshal.FreeHGlobal(_buffer);
    }
}

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

 using (MemoryStream stream = new MemoryStream()) 
{
    // ... 
}

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

   ~MyClass()
    {
        Dispose();
    }

内存溢出

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

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

其中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;

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

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

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

  • 7
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C#中,当你声明一个数组并为其分配超过可用内存的元素时,也会发生内存溢出。这种情况下,你将无法分配足够的内存来存储所有元素,导致程序崩溃或出现错误。 与C和C++不同,C#在运行时会对数组边界进行检查,如果尝试访问超出数组边界的元素,将会引发IndexOutOfRangeException异常。这是C#的一种安全机制,以防止数组溢出和访问无效的内存。 下面是一个示例,展示了在C#中如何声明一个超大数组并导致内存溢出情况: ```csharp int[] largeArray = new int[int.MaxValue]; ``` 上面的代码尝试声明一个包含int.MaxValue个元素的数组。由于int.MaxValue的值非常大(约为20亿),这将超出C#可用的内存范围,最终导致内存溢出。 要解决超大数组内存溢出的问题,你可以考虑以下方法: 1. 优化算法和数据结构:如果你需要处理大量数据,可以尝试使用更高效的算法和数据结构,例如使用流式处理或分块处理来减少内存使用量。 2. 分批处理:如果你的应用程序需要处理大量数据,但不需要同时加载整个数组,你可以尝试分批处理数据,只加载部分数据到内存中。 3. 使用压缩算法:如果你的数据可以被压缩,你可以尝试使用压缩算法来减少内存使用量。 4. 使用数据库或文件存储:如果你的数据量过大无法存储在内存中,你可以考虑使用数据库或文件存储来处理和管理数据。 总而言之,为了避免C#中的超大数组内存溢出问题,你需要使用合适的算法和数据结构,并考虑分批处理或使用其他存储方式来处理大量数据。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值