浅析c#内存泄漏

一直以来都对内存泄露和内存溢出理解的不是很深刻。在网上看到了几篇文章,于是整理了一下自己对内存泄露和内存溢出的理解。

一.概念

内存溢出:指程序在运行的过程中,程序对内存的需求超过了超过了计算机分配给程序的内存,从而造成“Out of memory”之类的错误,使程序不能正常运行。

造成内存溢出有几种情况: 1.计算机本身的内存小,当同时运行多个软件时,计算机得内存不够用从而造成内存溢出。对于这种情况,只能增加计算机内存来解决。 2.软件程序的问题,程序在运行时没能及时释放不用的内存,造成使用的内存越来越大从而造成内存溢出。对于这种情况,可以修改程序的代码来解决。

内存泄露:内存泄漏指由于疏忽或错误造成程序不能释放或不能及时释放已经不再使用的内存的情况,是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存不能回收和不能及时回收。当程序不能释放的内存越来越多是就会造成程序的性能下降或出现内存溢出的错误。

二、内存泄露检测工具:

1. SciTech Software AB .NET Memory Profiler-找到内存泄漏并优化内存使用针对C#,VB.Net,或其它.Net程序。

2. YourKit .NET & Java Profiler-业界领先的Java和.NET程序性能分析工具。

3. AutomatedQA AQTime-AutomatedQA的获奖产品performance profiling和memory debugging工具集的下一代替换产品,支持Microsoft, Borland, Intel, Compaq 和 GNU编译器。可以为.NET和Windows程序生成全面细致的报告,从而帮助您轻松隔离并排除代码中含有的性能问题和内存/资源泄露问题。支持.Net 1.0,1.1,2.0,3.0和Windows 32/64位应用程序。

4. JavaScript Memory Leak Detector-微软全球产品开发欧洲团队(Global Product Development- Europe team, GPDE) 发布的一款调试工具,用来探测JavaScript代码中的内存泄漏,运行为IE系列的一个插件。

5.使用LoadRunner,使用方法http://www.cnblogs.com/mayingbao/archive/2007/12/20/1006818.html

6.使用 .Net Memory Profiler 工具,使用方法见:http://lzy.iteye.com/blog/344317

7.在单元测试时,在代码中检测,如.net 下   使用Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));代码可以查看当前使用的内存。

二、导致内存泄露的常见情况及解决方法:

1.未退订的事件

是否没有手动注销事件就会造成内存泄露,我们先看这个问题

[csharp] view plaincopyprint?

  1. class TestClassHasEvent
  2. {
  3. public delegate void TestEventHandler(object sender, EventArgs e);
  4. public event TestEventHandler YourEvent;
  5. protected void OnYourEvent(EventArgs e)
  6. {
  7. if (YourEvent != null) YourEvent(this, e);
  8. }
  9. }
  10. class TestListener
  11. {
  12. byte[] m_ExtraMemory = new byte[1000000];
  13. private TestClassHasEvent _inject;
  14. public TestListener(TestClassHasEvent inject)
  15. {
  16. _inject = inject;
  17. _inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
  18. }
  19. void _inject_YourEvent(object sender, EventArgs e)
  20. {
  21. }
  22. }
  23. class Program
  24. {
  25. static void DisplayMemory()
  26. {
  27. Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
  28. }
  29. static void Main()
  30. {
  31. DisplayMemory();
  32. Console.WriteLine();
  33. for (int i = 0; i < 5; i++)
  34. {
  35. Console.WriteLine("--- New Listener #{0} ---", i + 1);
  36. var listener = new TestListener(new TestClassHasEvent());
  37. ////listener = null; //可有可无
  38. GC.Collect();
  39. GC.WaitForPendingFinalizers();
  40. GC.Collect();
  41. DisplayMemory();
  42. }
  43. Console.Read();
  44. }
  45. }
 class TestClassHasEvent
  {
      public delegate void TestEventHandler(object sender, EventArgs e);
      public event TestEventHandler YourEvent;
      protected void OnYourEvent(EventArgs e)
      {
          if (YourEvent != null) YourEvent(this, e);
      }
  }  

  class TestListener
  {
      byte[] m_ExtraMemory = new byte[1000000];  

      private TestClassHasEvent _inject;  

      public TestListener(TestClassHasEvent inject)
      {
          _inject = inject;
          _inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
      }  

      void _inject_YourEvent(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();
          for (int i = 0; i < 5; i++)
          {
              Console.WriteLine("--- New Listener #{0} ---", i + 1);  

              var listener = new TestListener(new TestClassHasEvent());
             ////listener = null; //可有可无  

              GC.Collect();
              GC.WaitForPendingFinalizers();
              GC.Collect();
              DisplayMemory();  

          }
          Console.Read();
      }
  }    

运行结果:

我们来改一行代码:

把下面这段:

[csharp] view plaincopyprint?

  1. public TestListener(TestClassHasEvent inject)
  2. {
  3. _inject = inject;
  4. _inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
  5. }
 public TestListener(TestClassHasEvent inject)
 {
     _inject = inject;
     _inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent);
 }

改成:

[csharp] view plaincopyprint?

  1. public TestListener(TestClassHasEvent inject)
  2. {
  3. SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
  4. }
  5. void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
  6. {
  7. }
 public TestListener(TestClassHasEvent inject)
 {
     SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
 }   

 void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
 {   

 }

看看运行结果:

内存泄露了

加个Dispose手动注销事件,然后使用Using关键字,就没有问题了

[csharp] view plaincopyprint?

  1. class TestListener : IDisposable
  2. {
  3. byte[] m_ExtraMemory = new byte[1000000];
  4. private TestClassHasEvent _inject;
  5. public TestListener(TestClassHasEvent inject)
  6. {
  7. SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
  8. }
  9. void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
  10. {
  11. }
  12. #region IDisposable Members
  13. public void Dispose()
  14. {
  15. SystemEvents.DisplaySettingsChanged -= new EventHandler(SystemEvents_DisplaySettingsChanged);
  16. }
  17. #endregion
  18. }
  19. class Program
  20. {
  21. static void DisplayMemory()
  22. {
  23. Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
  24. }
  25. static void Main()
  26. {
  27. DisplayMemory();
  28. Console.WriteLine();
  29. for (int i = 0; i < 5; i++)
  30. {
  31. Console.WriteLine("--- New Listener #{0} ---", i + 1);
  32. using (var listener = new TestListener(new TestClassHasEvent()))
  33. {
  34. //do something
  35. }
  36. GC.Collect();
  37. GC.WaitForPendingFinalizers();
  38. GC.Collect();
  39. DisplayMemory();
  40. }
  41. Console.Read();
  42. }
  43. }
 class TestListener : IDisposable
 {
     byte[] m_ExtraMemory = new byte[1000000];  

     private TestClassHasEvent _inject;   

     public TestListener(TestClassHasEvent inject)
     {
         SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
     }  

     void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
     {  

     }  

     #region IDisposable Members  

     public void Dispose()
      {
         SystemEvents.DisplaySettingsChanged -= new EventHandler(SystemEvents_DisplaySettingsChanged);
     }  

     #endregion
 }  

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

     static void Main()
     {
         DisplayMemory();
         Console.WriteLine();
         for (int i = 0; i < 5; i++)
         {
              Console.WriteLine("--- New Listener #{0} ---", i + 1);  

             using (var listener = new TestListener(new TestClassHasEvent()))
             {
                  //do something
              }
             GC.Collect();
              GC.WaitForPendingFinalizers();
             GC.Collect();
             DisplayMemory();  

         }
         Console.Read();
     }
 }

上面两个例子一个内存泄露,一个没有内存泄露,我想你应该知道原因了,根本区别在于后者有个SystemEvents.DisplaySettingsChanged事件,这个事件是静态Static事件,所以绑定到这个事件上的对象都不会被释放

[csharp] view plaincopyprint?

  1. // Type: Microsoft.Win32.SystemEvents
  2. // Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
  3. // Assembly location: C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.dll
  4. using System;
  5. using System.ComponentModel;
  6. namespace Microsoft.Win32
  7. {
  8. public sealed class SystemEvents
  9. {
  10. public static IntPtr CreateTimer(int interval);
  11. public static void InvokeOnEventsThread(Delegate method);
  12. public static void KillTimer(IntPtr timerId);
  13. public static event EventHandler DisplaySettingsChanging;
  14. public static event EventHandler DisplaySettingsChanged;
  15. public static event EventHandler EventsThreadShutdown;
  16. public static event EventHandler InstalledFontsChanged;
  17. [EditorBrowsable(EditorBrowsableState.Never)]
  18. [Obsolete("This event has been deprecated. http://go.microsoft.com/fwlink/?linkid=14202")]
  19. [Browsable(false)]
  20. public static event EventHandler LowMemory;
  21. public static event EventHandler PaletteChanged;
  22. public static event PowerModeChangedEventHandler PowerModeChanged;
  23. public static event SessionEndedEventHandler SessionEnded;
  24. public static event SessionEndingEventHandler SessionEnding;
  25. public static event SessionSwitchEventHandler SessionSwitch;
  26. public static event EventHandler TimeChanged;
  27. public static event TimerElapsedEventHandler TimerElapsed;
  28. public static event UserPreferenceChangedEventHandler UserPreferenceChanged;
  29. public static event UserPreferenceChangingEventHandler UserPreferenceChanging;
  30. }
  31. }
// Type: Microsoft.Win32.SystemEvents
// Assembly: System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\System.dll

 using System;
 using System.ComponentModel;

 namespace Microsoft.Win32
 {
     public sealed class SystemEvents
     {
         public static IntPtr CreateTimer(int interval);
         public static void InvokeOnEventsThread(Delegate method);
         public static void KillTimer(IntPtr timerId);
         public static event EventHandler DisplaySettingsChanging;
         public static event EventHandler DisplaySettingsChanged;
         public static event EventHandler EventsThreadShutdown;
         public static event EventHandler InstalledFontsChanged;

         [EditorBrowsable(EditorBrowsableState.Never)]
         [Obsolete("This event has been deprecated. http://go.microsoft.com/fwlink/?linkid=14202")]
         [Browsable(false)]
         public static event EventHandler LowMemory;

         public static event EventHandler PaletteChanged;
         public static event PowerModeChangedEventHandler PowerModeChanged;
         public static event SessionEndedEventHandler SessionEnded;
         public static event SessionEndingEventHandler SessionEnding;
         public static event SessionSwitchEventHandler SessionSwitch;
         public static event EventHandler TimeChanged;
         public static event TimerElapsedEventHandler TimerElapsed;
         public static event UserPreferenceChangedEventHandler UserPreferenceChanged;
         public static event UserPreferenceChangingEventHandler UserPreferenceChanging;
       }
}

注意Static,注意Singleton 这种static的东西生命周期很长,永远不会被GC回收,一旦被他给引用上了,那就不可能释放了。上面的例子就是SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);那就意味着这个类被SystemEvents.DisplaySettingsChanged 引用了,通过它的函数。另外一个要注意的是Singleton单例模式实现的类,他们也是static的生命周期很长,要注意引用链,你的类是否被它引用上,如果在它的引用链上,就内存泄露了。

另外还有注意程序运行期间不会释放的对象的事件

还有一种情况,既不是你的对象被static对象而不能释放,也不是Singleton,而是你的对象被一个永远不释放的对象引用着,这个对象或许不是static的。这种类型很多,比如你的界面有个MainForm,嘿嘿,这个MainForm永远不会关闭和释放的,被它引用了那就不会释放了。看个例子:

MainForm里面有个public event,MainForm里面打开Form2,然后关闭,看看Form2能不能释放:

[csharp] view plaincopyprint?

  1. public partial class MainForm : Form
  2. {
  3. public event PropertyChangedEventHandler PropertyChanged;
  4. protected virtual void OnPropertyChanged(string propertyName)
  5. {
  6. PropertyChangedEventHandler handler = PropertyChanged;
  7. if (handler != null)
  8. handler(this, new PropertyChangedEventArgs(propertyName));
  9. }
  10. public MainForm()
  11. {
  12. InitializeComponent();
  13. }
  14. private void button1_Click(object sender, EventArgs e)
  15. {
  16. Form2 frm = new Form2();
  17. this.PropertyChanged += frm.frm_PropertyChanged;
  18. //MainForm referenced form2, because main form is not released, therefore form2 will not released.
  19. DialogResult d = frm.ShowDialog();
  20. GC.Collect();
  21. ShowTotalMemory();
  22. }
  23. private void ShowTotalMemory()
  24. {
  25. this.listBox1.Items.Add(string.Format("Memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true)));
  26. }
  27. }
 public partial class MainForm : Form
 {
     public event PropertyChangedEventHandler PropertyChanged;   

    protected virtual void OnPropertyChanged(string propertyName)
    {
         PropertyChangedEventHandler handler = PropertyChanged;   

         if (handler != null)
             handler(this, new PropertyChangedEventArgs(propertyName));
     }  

     public MainForm()
     {
         InitializeComponent();
     }  

     private void button1_Click(object sender, EventArgs e)
     {
         Form2 frm = new Form2(); 

         this.PropertyChanged += frm.frm_PropertyChanged;
         //MainForm referenced form2, because main form is not released, therefore form2 will not released.  

         DialogResult d = frm.ShowDialog();  

         GC.Collect();
         ShowTotalMemory();  

     }  

     private void ShowTotalMemory()
     {
         this.listBox1.Items.Add(string.Format("Memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true)));
     }
 }

Form2里面有个函数:

[csharp] view plaincopyprint?

  1. public partial class Form2 : Form
  2. {
  3. public Form2()
  4. {
  5. InitializeComponent();
  6. }
  7. public void frm_PropertyChanged(object sender, PropertyChangedEventArgs e)
  8. {
  9. }
  10. }
public partial class Form2 : Form
 {
     public Form2()
     {
         InitializeComponent();
     }
     public void frm_PropertyChanged(object sender, PropertyChangedEventArgs e)
     {   

     }
 }

所以这种情况下,你的Event handler没有手动注销,那就肯定内存泄露了。

2.静态变量

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

3.非托管资源

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

4.Dispose方法没被调用,或Dispose方法没有处理对象的释放。这样也会造成内存泄露

5.当一个查询语句查询出来的数据量很大,达到几百万条数据时存放到datatable 或dataset中也会造成内存溢出,这是可以采用分页查询等其他方法来解决

时间: 2024-08-26 17:21:27

浅析c#内存泄漏的相关文章

浅析Context及可能带来的内存泄漏问题

什么是 Context 纯英文含义来看,Context 意指上下文.环境.背景等等--那么 Android 中的 Context 的含义和这些英文释义有什么联系呢?不妨看看 Google 给出的定义: Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. I

urlopen内存泄漏浅析

1.背景 urllib,urllib2是客户端http协议的实现,urllib2底层使用httplib,socket库,它主要包含urlopen, build_opener, install_opener等func.python2.7使用urllib2库中的urlopen会出现内存泄漏的现象,可以通过gc模块来视察内存泄漏情况. # -*- coding: utf-8 -*- #!usr/bin/python import urllib2 import socket import gc # ch

java的GC与内存泄漏

从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C语言的同学都知道,在C语言中内存的开辟和释放都是由我们自己来管理的,每一个new操作都要对于一个delete操作,否则就会参数内存泄漏和溢出的问题,导致非常槽糕的后果.但在Java开发过程中,则完全不需要担心这个问题.因为jvm提供了自动内存管理的机制.内存管理的工作由jvm帮我们完成.这样我们就不

检查内存泄漏

1.分配空间 2.记录内存块信息 3.调用构造函数(类型萃取) #include<iostream> #include<string> #include<list> #include<assert.h> using namespace std; struct BlockInfo { void* _ptr; string _file; int _line; BlockInfo(void *ptr, const char*file, int line) :_pt

内存泄漏工具VLD1.0_要点分析

0X01 关闭FPO优化 // Frame pointer omission (FPO) optimization should be turned off for this // entire file. The release VLD libs don't include FPO debug information, so FPO // optimization will interfere with stack walking. #pragma optimize ("y", of

内存泄漏-Node

内存泄漏: 1.缓存 2.队列消费不及时 3.作用域未释放 缓存: 必须要有过期策略 1.缓存限制策略 limitablemap LRU 2.缓存解决方案 进程自身不存储状态,进程外缓存 1)能减少常驻内存的对象的数量,让垃圾回收更高效 2)进程之间可以共享缓存 常用的缓存: Redis Memcached 内存泄漏-Node,布布扣,bubuko.com

只运行一个实例以及内存泄漏检测

unit 使应用程序只运行一个实例; interface uses Windows; const  // - 互斥体唯一的名字  _Mutex_Name = '{19631971-1976-1981-1989-199319941995}'; var  _Mutex_Handle: THandle; implementation initialization // - 载入时调用的代码 // - 创建互斥体对象_Mutex_Handle := CreateMutex(nil, False, LPC

SGI STL内存配置器存在内存泄漏吗?

阅读了SGI的源码后对STL很是膜拜,很高质量的源码,从中学到了很多.温故而知新!下文中所有STL如无特殊说明均指SGI版本实现. STL 内存配置器 STL对内存管理最核心部分我觉得是其将C++对象创建过程分解为构造.析构和内存分配.释放两类操作分离开来!摆脱了对频繁调用new或malloc函数想操作系统申请空间而造成的低效.其中析构操作时对具有non-trival.trival 析构函数的class区别对待也提高了效率.SGI 的两级配置器结构属于锦上添花. STL内存配置器有没有内存泄漏?

Android开发 |常见的内存泄漏问题及解决办法

在Android开发中,内存泄漏是比较常见的问题,有过一些Android编程经历的童鞋应该都遇到过,但为什么会出现内存泄漏呢?内存泄漏又有什么影响呢? 在Android程序开发中,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了. 内存泄漏有什么影响呢?它是造成应用程序OOM的主要原因之一.由于Android系统为每个应用程序分配的内存有限,当一个应用中产生的内存泄漏比较多时