一直以来都对内存泄露和内存溢出理解的不是很深入。在网上看到了几篇文章,因而整理了一下本身对内存泄露和内存溢出的理解。html
内存溢出:指程序在运行的过程当中,程序对内存的需求超过了超过了计算机分配给程序的内存,从而形成“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系列的一个插件。this
5.使用LoadRunner,使用方法http://www.cnblogs.com/mayingbao/archive/2007/12/20/1006818.htmlspa
6.使用 .Net Memory Profiler 工具,使用方法见:http://lzy.iteye.com/blog/344317.net
7.在单元测试时,在代码中检测,如.net 下 使用Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));代码能够查看当前使用的内存。
1.未退订的事件
是否没有手动注销事件就会形成内存泄露,咱们先看这个问题
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(); } }
运行结果:
咱们来改一行代码:
把下面这段:
public TestListener(TestClassHasEvent inject) { _inject = inject; _inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent); }
改为:
public TestListener(TestClassHasEvent inject) { SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged); } void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) { }
看看运行结果:
内存泄露了
加个Dispose手动注销事件,而后使用Using关键字,就没有问题了
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事件,因此绑定到这个事件上的对象都不会被释放
// 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能不能释放:
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里面有个函数:
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中也会形成内存溢出,这是能够采用分页查询等其余方法来解决