一直以來都對(duì)內(nèi)存泄露和內(nèi)存溢出理解的不是很深刻。在網(wǎng)上看到了幾篇文章,于是整理了一下自己對(duì)內(nèi)存泄露和內(nèi)存溢出的理解。
一.概念 內(nèi)存溢出: 指程序在運(yùn)行的過程中,程序?qū)?nèi)存的需求超過了超過了計(jì)算機(jī)分配給程序的內(nèi)存,從而造成“Out of memory”之類的錯(cuò)誤,使程序不能正常運(yùn)行。
造成內(nèi)存溢出有幾種情況: 1.計(jì)算機(jī)本身的內(nèi)存小,當(dāng)同時(shí)運(yùn)行多個(gè)軟件時(shí),計(jì)算機(jī)得內(nèi)存不夠用從而造成內(nèi)存溢出。對(duì)于這種情況,只能增加計(jì)算機(jī)內(nèi)存來解決。 2.軟件程序的問題,程序在運(yùn)行時(shí)沒能及時(shí)釋放不用的內(nèi)存,造成使用的內(nèi)存越來越大從而造成內(nèi)存溢出。對(duì)于這種情況,可以修改程序的代碼來解決。
內(nèi)存泄露: 內(nèi)存泄漏指由于疏忽或錯(cuò)誤造成程序不能釋放或不能及時(shí)釋放已經(jīng)不再使用的內(nèi)存的情況,是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)計(jì)錯(cuò)誤,失去了對(duì)該段內(nèi)存的控制,因而造成了內(nèi)存不能回收和不能及時(shí)回收。當(dāng)程序不能釋放的內(nèi)存越來越多是就會(huì)造成程序的性能下降或出現(xiàn)內(nèi)存溢出的錯(cuò)誤。
二、內(nèi)存泄露檢測(cè)工具: 1. SciTech Software AB .NET Memory Profiler-找到內(nèi)存泄漏并優(yōu)化內(nèi)存使用針對(duì)C#,VB.Net,或其它.Net程序。
2. YourKit .NET & Java Profiler-業(yè)界領(lǐng)先的Java和.NET程序性能分析工具。
3. AutomatedQA AQTime-AutomatedQA的獲獎(jiǎng)產(chǎn)品performance profiling和memory debugging工具集的下一代替換產(chǎn)品,支持Microsoft, Borland, Intel, Compaq 和 GNU編譯器。可以為.NET和Windows程序生成全面細(xì)致的報(bào)告,從而幫助您輕松隔離并排除代碼中含有的性能問題和內(nèi)存/資源泄露問題。支持.Net 1.0,1.1,2.0,3.0和Windows 32/64位應(yīng)用程序。
4. JavaScript Memory Leak Detector-微軟全球產(chǎn)品開發(fā)歐洲團(tuán)隊(duì)(Global Product Development- Europe team, GPDE) 發(fā)布的一款調(diào)試工具,用來探測(cè)JavaScript代碼中的內(nèi)存泄漏,運(yùn)行為IE系列的一個(gè)插件。
5.使用LoadRunner ,使用方法http://www.cnblogs.com/mayingbao/archive/2007/12/20/1006818.html
6.使用 .Net Memory Profiler 工具,使用方法見:http://lzy.iteye.com/blog/344317
7.在單元測(cè)試時(shí),在代碼中檢測(cè),如.net 下?? 使用Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));代碼可以查看當(dāng)前使用的內(nèi)存。
二、導(dǎo)致內(nèi)存泄露的常見情況及解決方法: 1.未退訂的事件
是否沒有手動(dòng)注銷事件就會(huì)造成內(nèi)存泄露,我們先看這個(gè)問題
[csharp] ?view plaincopyprint?
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());???? ???????????? ????????????????? ?????????????GC.Collect();???? ?????????????GC.WaitForPendingFinalizers();???? ?????????????GC.Collect();???? ?????????????DisplayMemory();???? ????????????????? ?????????}???? ?????????Console.Read();???? ?????}???? ?}?????? 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(); } }
運(yùn)行結(jié)果:?
我們來改一行代碼:
把下面這段:
[csharp] ?view plaincopyprint?
public?TestListener(TestClassHasEvent?inject)????? {????? ????_inject?=?inject;????? ????_inject.YourEvent?+=?new?TestClassHasEvent.TestEventHandler(_inject_YourEvent);????? }?? public TestListener(TestClassHasEvent inject) { _inject = inject; _inject.YourEvent += new TestClassHasEvent.TestEventHandler(_inject_YourEvent); }
改成:
[csharp] ?view plaincopyprint?
public?TestListener(TestClassHasEvent?inject)????? {????? ????SystemEvents.DisplaySettingsChanged?+=?new?EventHandler(SystemEvents_DisplaySettingsChanged);????? }????? ?????? void?SystemEvents_DisplaySettingsChanged(object?sender,?EventArgs?e)????? {????? ??????? }?? public TestListener(TestClassHasEvent inject) { SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged); } void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) { }
看看運(yùn)行結(jié)果:
?內(nèi)存泄露了
加個(gè)Dispose手動(dòng)注銷事件,然后使用Using關(guān)鍵字,就沒有問題了
[csharp] ?view plaincopyprint?
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()))???? ????????????{???? ????????????????? ?????????????}???? ????????????GC.Collect();???? ?????????????GC.WaitForPendingFinalizers();???? ????????????GC.Collect();???? ????????????DisplayMemory();???? ???????????????? ????????}???? ????????Console.Read();???? ????}???? }?? 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(); } }
上面兩個(gè)例子一個(gè)內(nèi)存泄露,一個(gè)沒有內(nèi)存泄露,我想你應(yīng)該知道原因了,根本區(qū)別在于后者有個(gè)SystemEvents.DisplaySettingsChanged事件,這個(gè)事件是靜態(tài)Static事件,所以綁定到這個(gè)事件上的對(duì)象都不會(huì)被釋放
[csharp] ?view plaincopyprint?
??? ?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;?? ???????}?? }?? // 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.dllusing 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的東西生命周期很長(zhǎng),永遠(yuǎn)不會(huì)被GC回收,一旦被他給引用上了,那就不可能釋放了。上面的例子就是SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);那就意味著這個(gè)類被SystemEvents.DisplaySettingsChanged 引用了,通過它的函數(shù)。另外一個(gè)要注意的是Singleton單例模式實(shí)現(xiàn)的類,他們也是static的生命周期很長(zhǎng),要注意引用鏈,你的類是否被它引用上,如果在它的引用鏈上,就內(nèi)存泄露了。
另外還有注意程序運(yùn)行期間不會(huì)釋放的對(duì)象的事件
還有一種情況,既不是你的對(duì)象被static對(duì)象而不能釋放,也不是Singleton,而是你的對(duì)象被一個(gè)永遠(yuǎn)不釋放的對(duì)象引用著,這個(gè)對(duì)象或許不是static的。這種類型很多,比如你的界面有個(gè)MainForm,嘿嘿,這個(gè)MainForm永遠(yuǎn)不會(huì)關(guān)閉和釋放的,被它引用了那就不會(huì)釋放了。看個(gè)例子:
MainForm里面有個(gè)public event,MainForm里面打開Form2,然后關(guān)閉,看看Form2能不能釋放:
[csharp] ?view plaincopyprint?
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;????? ???????? ????? ????????DialogResult?d?=?frm.ShowDialog();???? ???????????? ????????GC.Collect();???? ????????ShowTotalMemory();???? ????? ????}???? ????? ???????? ???? ????private?void?ShowTotalMemory()???? ????{???? ????????this.listBox1.Items.Add(string.Format("Memory:?{0:###,###,###,##0}?bytes",?GC.GetTotalMemory(true)));???? ????}???? }?? 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里面有個(gè)函數(shù):
[csharp] ?view plaincopyprint?
public?partial?class?Form2?:?Form????? ?{????? ?????public?Form2()????? ?????{????? ?????????InitializeComponent();????? ?????}????? ?????public?void?frm_PropertyChanged(object?sender,?PropertyChangedEventArgs?e)????? ?????{????? ?????? ?????}???? ?}?? public partial class Form2 : Form { public Form2() { InitializeComponent(); } public void frm_PropertyChanged(object sender, PropertyChangedEventArgs e) { } }
所以這種情況下,你的Event handler沒有手動(dòng)注銷,那就肯定內(nèi)存泄露了。
2.靜態(tài)變量
靜態(tài)變量中的成員所占的內(nèi)存不果不手動(dòng)處理是不會(huì)釋放內(nèi)存的,單態(tài)模式的對(duì)象也是靜態(tài)的,所以需要特別注意。因?yàn)殪o態(tài)對(duì)象中的成員所占的內(nèi)存不會(huì)釋放,如果此成員是以個(gè)對(duì)象,同時(shí)此對(duì)象中的成員所占的內(nèi)存也不會(huì)釋放,以此類推,如果此對(duì)象很復(fù)雜,而且是靜態(tài)的就很容易造成內(nèi)存泄露。
3.非托管資源
因?yàn)榉峭泄苜Y源所占的內(nèi)存不能自動(dòng)回收,所以使用后必須手動(dòng)回收,否則程序運(yùn)行多次很容易造成內(nèi)存泄露
4.Dispose方法沒被調(diào)用,或Dispose方法沒有處理對(duì)象的釋放。這樣也會(huì)造成內(nèi)存泄露
5.當(dāng)一個(gè)查詢語句查詢出來的數(shù)據(jù)量很大,達(dá)到幾百萬條數(shù)據(jù)時(shí)存放到datatable 或dataset中也會(huì)造成內(nèi)存溢出,這是可以采用分頁(yè)查詢等其他方法來解決
總結(jié)
以上是生活随笔 為你收集整理的浅析c#内存泄漏 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。