.NET中的內(nèi)存管理,GC機制,內(nèi)存釋放過程_第1頁
.NET中的內(nèi)存管理,GC機制,內(nèi)存釋放過程_第2頁
.NET中的內(nèi)存管理,GC機制,內(nèi)存釋放過程_第3頁
.NET中的內(nèi)存管理,GC機制,內(nèi)存釋放過程_第4頁
.NET中的內(nèi)存管理,GC機制,內(nèi)存釋放過程_第5頁
已閱讀5頁,還剩8頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

?NET中的內(nèi)存管理,GC機制,內(nèi)存釋放過程分類:C#2012-07-0709:56671人閱讀評論(0)收藏舉報.netdestructorgenerationclassbasicinteger引言作為一個.NET程序員,我們知道托管代碼的內(nèi)存管理是自動的。.NET可以保證我們的托管程序在結(jié)束時全部釋放,這為我們編程人員省去了不少麻煩,我們可以連想都不想怎么去管理內(nèi)存,反正.NET自己會保證一切。好吧,有道理,有一定的道理。問題是,當我們用到非托管資源時.NET就不能自動管理了。這是因為非托管代碼不受CLR(CommonLanguageRuntime)控制,超出CLR的管理范圍。那么如何處理這些非托管資源呢,NET又是如何管理并釋放托管資源的呢?自動內(nèi)存管理和GC在原始程序中堆的內(nèi)存分配是這樣的:找到第一個有足夠空間的內(nèi)存地址(沒被占用的),然后將該內(nèi)存分配。當程序不再需要此內(nèi)存中的信息時程序員需要手動將此內(nèi)存釋放。堆的內(nèi)存是公用的,也就是說所有進程都有可能覆蓋另一進程的內(nèi)存內(nèi)容,這就是為什么很多設計不當?shù)某绦蛏踔習尣僮飨到y(tǒng)本身都down掉。我們有時碰到的程序莫名其妙的死掉了(隨機現(xiàn)象),也是因為內(nèi)存管理不當引起的(可能由于本身程序的內(nèi)存問題或是外來程序造成的)。另一個常見的實例就是大家經(jīng)常看到的游戲的Trainer,他們通過直接修改游戲的內(nèi)存達到"無敵"的效果。明白了這些我們可以想象如果內(nèi)存地址被用混亂了的話會多么危險,我們也可以想象為什么C++程序員(某些)一提起指針就頭疼的原因了。另外,如果程序中的內(nèi)存不被程序員手動釋放的話那么這個內(nèi)存就不會被重新分配,直到電腦重起為止,也就是我們所說的內(nèi)存泄漏。所說的這些是在非托管代碼中,CLR通過AppDomain實現(xiàn)代碼間的隔離避免了這些內(nèi)存管理問題,也就是說一個AppDomain在一般情況下不能讀/寫另一AppDomain的內(nèi)存。托管內(nèi)存釋放就由GC(GarbageCollector)來負責。我們要進一步講述的就是這個GC,但是在這之前要先講一下托管代碼中內(nèi)存的分配,托管堆中內(nèi)存的分配是順序的,也就是說一個挨著一個的分配。這樣內(nèi)存分配的速度就要比原始程序高,但是高出的速度會被GC找回去。為什么?看過GC的工作方式后你就會知道答案了。GC工作方式首先我們要知道托管代碼中的對象什么時候回收我們管不了(除非用GC.Collect強迫GC回收,這不推薦,后面會說明為什么)。GC會在它"高興"的時候執(zhí)行一次回收(這有許多原因,比如內(nèi)存不夠用時。這樣做是為了提高內(nèi)存分配、回收的效率)。那么如果我們用Destructor呢?同樣不行,因為.NET中Destructor的概念已經(jīng)不存在了,它變成了Finalizer,這會在后面講到。目前請記住一個對象只有在沒有任何引用的情況下才能夠被回收。為了說明這一點請看下面這一段代碼:[c#]objectobjA=newobject();objectobjB=objA;objA=null;//強迫回收。GC.Collect();objB.ToStringO;[VisualBasic]DimobjAAsNewObject()DimobjBAsObject二objAobjA=Nothing'強迫回收。GC.Collect()objB.ToString()這里objA引用的對象并沒有被回收,因為這個對象還有另一個引用,ObjB。對象在沒有任何引用后就有條件被回收了。當GC回收時,它會做以下幾步:確定對象沒有任何引用。檢查對象是否在Finalizer表上有記錄。如果在Finalizer表上有記錄,那么將記錄移到另外的一張表上,在這里我們叫它Finalizer2。如果不在Finalizer2表上有記錄,那么釋放內(nèi)存。在Finalizer2表上的對象的Finalizer會在另外一個lowpriority的線程上執(zhí)行后從表上刪除。當對象被創(chuàng)建時GC會檢查對象是否有Finalizer,如果有就會在Finalizer表中添加紀錄。我們這里所說的記錄其實就是指針。如果仔細看這幾個步驟,我們就會發(fā)現(xiàn)有Finalizer的對象第一次不會被回收,也就是,有Finalizer的對象要一次以上的Collect操作才會被回收,這樣就要慢一步,所以作者推薦除非是絕對需要不要創(chuàng)建Finalizer。為了證明GC確實這么工作而不是作者胡說,我們將在對象的復活一章中給出一個示例,眼見為實,耳聽為虛嘛!AAGC為了提高回收的效率使用了Generation的概念,原理是這樣的,第一次回收之前創(chuàng)建的對象屬于Generation0,之后,每次回收時這個Generation的號碼就會向后挪一,也就是說,第二次回收時原來的Generation0變成了Generation1而在第一次回收后和第二次回收前創(chuàng)建的對象將屬于Generation0。GC會先試著在屬于Generation0的對象中回收,因為這些是最新的,所以最有可能會被回收,比如一些函數(shù)中的局部變量在退出函數(shù)時}}}就沒有引用了(可被回收)。如果在Generation0中回收了足夠的內(nèi)存,那么GC就不會再接著回收了,如果回收的還不夠,那么GC就試著在Generation1中回收,如果還不夠就在Generation2中回收,以此類推。Generation也有個最大限制,根據(jù)Framework版本而定,可以用GC.MaxGeneration獲得。在回收了內(nèi)存之后GC會重新排整內(nèi)存,讓數(shù)據(jù)間沒有空格,這樣是因為CLR順序分配內(nèi)存,所以內(nèi)存之間不能有空著的內(nèi)存。現(xiàn)在我們知道每次回收時都會浪費一定的CPU時間,這就是我說的一般不要手動GC.Collect的原因(除非你也像我一樣,寫一些有關(guān)GC的示例!A_A)0Destructor的沒落,F(xiàn)inalizer的誕生對于VisualBasic程序員來說這是個新概念,所以前一部分講述將著重對C++程序員。我們知道在C++中當對象被刪除時(delete),Destructor中的代碼會馬上執(zhí)行來做一些內(nèi)存釋放工作(或其他)。不過在.NET中由于GC的特殊工作方式,Destructor并不實際存在,事實上,當我們用Destructor的語法時,編譯器會自動將它寫為protectedvirtualvoidFinalize(),這個方法就是我所說的Finalizer。就象它的名字所說,它用來結(jié)束某些事物,不是用來摧毀(Destruct)事物。在VisualBasic中它就是以Finalize方法的形式出現(xiàn)的,所以VisualBasic程序員就不用操心了。C#程序員得用Destructor的語法寫Finalizer,不過千萬不要弄混了,.NET中已經(jīng)沒有Destructor了。C++中我們可以準確的知道什么時候會執(zhí)行Destructor,不過在.NET中我們不能知道什么時候會執(zhí)行Finalizer,因為它是在第一次對象回收操作后才執(zhí)行的。我們也不能知道Finalizer的執(zhí)行順序,也就是說同樣的情況下,A的Finalize可能先被執(zhí)行,B的后執(zhí)行,也可能A的后執(zhí)行而B的先執(zhí)行。也就是說,在Finalizer中我們的代碼不能有任何的時間邏輯。下面我們以計算一個類有多少個實例為示例,指出Finalizer與Destructor的不同并指出在Finalizer中有時間邏輯的錯誤,因為VisualBasic中沒有過Destructor所以示例只有C#版:[C#]publicclassCountObject{publicstaticintCount二0;publicCountObject(){Count++;}~CountObject(){Count--;staticvoidMain(){CountObjectobj;for(inti二0;i<5;i++){obj=null;//這一步多余,這么寫只是為了更清晰些!obj=newCountObject();}//Count不會是1,因為Finalizer不會馬上被觸發(fā),要等到有一次回收操作后才會被觸發(fā)。Console.WriteLine(CountObject.Count);Console.ReadLine();}注意以上代碼要是改用C++寫的話會發(fā)生內(nèi)存泄漏,因為我們沒有用delete操作符手動清理內(nèi)存,但是在托管代碼中卻不會發(fā)生內(nèi)存泄漏,因為GC會自動檢測沒有引用了的對象并回收。這里作者推薦你只在實現(xiàn)Disposable接口時配合使用Finalizer,在其他的情況下不要使用(可能會有特殊情況)。在非托管資源的釋放一章我們會更好的了解IDisposable接口,現(xiàn)在讓我們來做耶穌吧!對象的復活什么?回收的對象也可以"復活"嗎?沒錯,雖然這么說的定義不準確。讓我們先來看一段代碼:[C#]publicclassResurrection{publicintData;publicResurrection(intdata){this.Data=data;}~Resurrection(){Main.Instance=this;publicclassMain{publicstaticResurrectionInstance;publicstaticvoidMain(){Instance=newResurrection(l);Instance=null;GC.Collect();GC.WaitForPendingFinalizers();//看到了嗎,在這里“復活”了。Console.WriteLine(Instance.Data);Instance=null;GC.Collect();Console.ReadLine();}}[VisualBasic]PublicClassResurrectionPublicDataAsIntegerPublicSubNew(ByValdataAsInteger)Me.Data=dataEndSubProtectedOverridesSubFinalize()Main.Instance=MeMyBase.FinalizeOEndSubEndClassPublicClassMainPublicSharedInstanceAsResurrectionSubMain()Instance=NewResurrection(l)Instance=NothingGC.Collect()GC.WaitForPendingFinalizers()'看到了嗎,在這里“復活”了。Console.WriteLine(Instance.Data)Instance=NothingGC.Collect()Console.ReadLine()EndSubEndClass你可能會問:"既然這個對象能復活,那么這個對象在程序結(jié)束后會被回收嗎?"。會,"為什么?"。讓我們按照GC的工作方式走一遍你就明白是怎么回事了。1、執(zhí)行Collect。檢查引用。沒問題,對象已經(jīng)沒有引用了。2、創(chuàng)建新實例時已經(jīng)在Finalizer表上作了紀錄,所以我們檢查到了對象有Finalizer。3、因為查到了Finalizer,所以將記錄移到Finalizer2表上。4、在Finalizer2表上有記錄,所以不釋放內(nèi)存。5、Collect執(zhí)行完畢。這時我們用了GC.WaitForPendingFinalizers,所以我們將等待所有Finalizer2表上的Finalizers的執(zhí)行。6、Finalizer執(zhí)行后我們的Instanee就又引用了我們的對象。(復活了)7、再一次去除所有的引用。&執(zhí)行Collecto檢查引用。沒問題。9、由于上次已經(jīng)將記錄從Finalizer表刪除,所以這次沒有查到對象有Finalizer。10、在Finalizer2表上也不存在,所以對象的內(nèi)存被釋放了。現(xiàn)在你明白原因了,讓我來告訴你"復活"的用處。嗯,這個?……好吧,我不知道。其實,復活沒有什么用處,而且這樣做也非常的危險。看來這只能說是GC機制的漏洞(請參看GC.ReRegisterForFinalize再動腦筋想一下就知道為什么可以說是漏洞了)。作者建議大家忘掉有什么復活,避免這類的使用。可能你會問:"那你干嗎還要對我們說這些?"我說這些為的是讓大家更好的了解GC的工作機制!A_A非托管資源的釋放到現(xiàn)在為止,我們說了托管內(nèi)存的管理,那么當我們利用如數(shù)據(jù)庫、文件等非托管資源時呢?這時我們就要用到.NETFramework中的標準「Disposable接口。按照標準,所有有需要手動釋放非托管資源的類都得實現(xiàn)此接口。這個接口只有一個方法,Dispose。,不過有相對的Guidelines指示如何實現(xiàn)此接口,在這里我向大家說一說。實現(xiàn)IDisposable這個接口的類需要有這樣的結(jié)構(gòu):[C#]publicclassBase:IDisposable{publicvoidDispose。{this.Dispose(true);GC.SupressFinalize(this);}protectedvirtualvoidDispose(booldisposing){if(disposing){//托管類}//非托管資源釋放}~Base(){this.Dispose(false);}}publicclassDerive:Base{protectedoverridevoidDispose(booldisposing){if(disposing){//托管類}//非托管資源釋放base.Dispose(disposing);}}[VisualBasic]PublicClassBaseImplements【DisposablePublicOverloadsSubDisposeOImplementsIDisposable.DisposeMe.Dispose(True)GC.SuppressFinalize(Me)EndSubProtectedOverloadsOverridableSubDispose(ByValdisposingAsBoolean)IfdisposingThen'托管類EndIf'非托管資源釋放EndSubProtectedOverridesSubFinalize()Me.Dispose(False)MyBase.FinalizeOEndSubEndClassPublicClassDeriveInheritsBaseProtectedOverloadsOverridesSubDispose(ByValdisposingAsBoolean)IfdisposingThen'托管類EndIf'非托管資源釋放MyBase.Dispose(disposing)EndSubEndClass為什么要這樣設計呢?讓我在后面解說一下。現(xiàn)在我們講講實現(xiàn)這個Dispose方法的幾個準則:它不能扔出任何錯誤,重復的調(diào)用也不能扔出錯誤。也就是說,如果我已經(jīng)調(diào)用了一個對象的Dispose,當我第二次調(diào)用Dispose的時候程序不應該出錯,簡單地說程序在第二次調(diào)用Dispose時不會做任何事。這些可以通過一個flag或多重if判斷實現(xiàn)。一個對象的Dispose要做到釋放這個對象的所有資源。拿一個繼承類為例,繼承類中用到了非托管資源所以它實現(xiàn)了Disposable接口,如果繼承類的基類也用到了非托管資源那么基類也得被釋放,基類的資源如何在繼承類中釋放呢?當然是通過一個virtual/Overridable方法了,這樣我們能保證每個Dispose都被調(diào)用到。這就是為什么我們的設計有一個virtual/Overridable的Dispose方法。注意我們首先要釋放繼承類的資源然后再釋放基類的資源。因為非托管資源一定要被保障正確釋放所以我們要定義一個Finalizer來避免程序員忘了調(diào)用Dispose的情況。上面的設計就采用了這種形式。如果我們手動調(diào)用Dispose方法就沒有必要再保留Finalizer了,所以在Dispose中我們用了GC.SupressFinalize將對象從Finalizer表去掉,這樣再回收時速度會更快。那么那個disposing和"托管類"是怎么回事呢?是這樣:在"托管類"中寫所有你想在調(diào)用Dispose時讓其處于可釋放狀態(tài)的托管代碼。還記得我們說過我們不知道托管代碼是什么時候釋放的嗎?在這里我們只是去掉成員對象的引用讓它處于可被回收狀態(tài),并不是直接釋放內(nèi)存。在"托管類"中這里我們也要寫上所有實現(xiàn)了IDisposable的成員對象,因為他們也有Dispose,所以也需要在對象的Dispose中調(diào)用他們的Dispose,這樣才能保證第二個準則。disposing是為了區(qū)分Dispose的調(diào)用方法,如果我們手動調(diào)用那么為了第二個準則"托管類"部分當然得執(zhí)行,但如果是Finalizer調(diào)用的Dispose,這時候?qū)ο笠呀?jīng)沒有任何引用,也就是說對象的成員自然也就不存在了(無引用),也就沒有必要執(zhí)行■托管類"部分了,因為他們已經(jīng)處于可被回收狀態(tài)了。好了,這就是Disposable接口的全部了。現(xiàn)在讓我們來回想一下,以前我們可能認為有了Dispose內(nèi)存就會馬上被釋放,這是錯誤的。只有非托管內(nèi)存才會被馬上釋放,托管內(nèi)存的釋放由GC管理,我們不用管。弱引用的使用A=B,我們稱這樣的引用叫做強引用,GC就是通過檢查強引用來決定一個對象是否是可以回收的。另外還有一種引用稱作弱引用(WeakReferenee),這種引用不影響GC回收,這就是它的用處所在。你會問到底有什么用處。現(xiàn)在我們來假設我們有一個很胖的對象,也就是說它占用很多內(nèi)存。我們用過了這個對象,打算將它的引用去掉好讓GC可以回收內(nèi)存,但是功夫不大我們又需要這個對象了,沒辦法,重新創(chuàng)建實例,怎么創(chuàng)建這么慢啊?有什么辦法解決這樣的問題?有,將對象留在內(nèi)存中不就快了嘛!不過我們不想這樣胖得對象總占著內(nèi)存,而我們也不想總是創(chuàng)建這樣胖的新實例,因為這樣很耗時。那怎么辦……?聰明的朋友一定已經(jīng)猜到了我要說解決方法是弱引用。不錯,就是它。我們可以創(chuàng)建一個這個胖對象的弱引用,這樣在內(nèi)存不夠時GC可以回收,不影響內(nèi)存使用,而在沒有被GC回收前我們還可以再次利用該對象。這里有一個示例:[C#]publicclassFat{publicintData;publicFat(intdata){this.Data=data;}}publicclassMain{publicstaticvoidMain(){FatoFat二newFat(l);WeakReferenceoFatRef=newWeakReference(oFat);//從這里開始,F(xiàn)at對象可以被回收了。oFat二null;if(oFatRef.IsAlive){Console.WriteLine(((Fat)oFatRef.Target).Data);//1}//強制回收。GC.Collect();Console.WriteLine(oFatRef.IsAlive);//FalseConsole.ReadLine();}}[VisualBasic]PublicClassFatPublicDataAsInt

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論