C#.Net GC(garbage Collector) 垃圾回收器

之前一直覺得gc的原理很簡單,也就是分代處理堆數據,直到個人膝蓋中了一箭(好吧 直到有天汪濤和我說他面試攜程的面試題 關於服務器和 工做站gc 的區別)其實我當時尚不知道 工做站和服務器有什麼區別更不要提其中的GC。html

話很少說  下面開始談論GC程序員

一GC的前世此生 ,二.NET垃圾回收機制面試

參考文獻算法

http://www.myexception.cn/c-sharp/1515938.html編程

http://blog.csdn.net/bingbing200x安全

http://blog.csdn.net/lerit/article/details/4451287服務器

 

一.GC的前世與此生數據結構

  雖然本文是以.net做爲目標來說述GC,可是GC的概念並不是才誕生不久。早在1958年,由鼎鼎大名的圖林獎得主John McCarthy所實現的Lisp語言就已經提供了GC的功能,這是GC的第一次出現。Lisp的程序員認爲內存管理過重要了,因此不能由程序員本身來管理。架構

  但後來的日子裏Lisp卻沒有成氣候,採用內存手動管理的語言佔據了上風,以C爲表明。出於一樣的理由,不一樣的人卻又不一樣的見解,C程序員認爲內存管理過重要了,因此不能由系統來管理,而且譏笑Lisp程序慢如烏龜的運行速度。的確,在那個對每個Byte都要精心計算的年代GC的速度和對系統資源的大量佔用使不少人的沒法接受。然後,1984年由Dave Ungar開發的Small talk語言第一次採用了Generational garbage collection的技術(這個技術在下文中會談到),可是Small talk也沒有獲得十分普遍的應用。併發

  直到20世紀90年代中期GC才以主角的身份登上了歷史的舞臺,這不得不歸功於Java的進步,今日的GC已非吳下阿蒙。Java採用VM(Virtual Machine)機制,由VM來管理程序的運行固然也包括對GC管理。90年代末期.net出現了,.net採用了和Java相似的方法由CLR(Common Language Runtime)來管理。這兩大陣營的出現將人們引入了以虛擬平臺爲基礎的開發時代,GC也在這個時候愈來愈獲得大衆的關注。

 

二.NET垃圾回收機制

 

1.導言

2.關於垃圾回收

3.垃圾回收的功能

4.託管堆和託管棧

5.垃圾收集器如何尋找再也不使用的託管對象並釋放其佔用的內存

a.根

b.建立一個圖 一個描述對象間引用關係的圖

        在.NET Framework中,內存中的資源(即全部二進制信息的集合)分爲"託管資源"和"非託管資源".託管資源必須接受.NET Framework的CLR(通用語言運行時)的管理(諸如內存類型安全性檢查),而非託管資源則沒必要接受.NET Framework的CLR管理. (瞭解更多區別請參閱.NET Framework或C#的高級編程資料) 
       託管資源在.NET Framework中又分別存放在兩種地方: "堆棧"和"託管堆"(如下簡稱"堆");規則是,全部的值類型(包括引用和對象實例)和引用類型的引用都存放在"堆棧"中,而全部引用所表明的對象實例都保存在堆中。
      在.NET中,釋放託管資源是能夠自動經過"垃圾回收器"完成的(注意,"垃圾回收"機制是.NET Framework的特性,而不是C#的),但具體來講,仍有些須要注意的地方:

1.值類型和引用類型的引用實際上是不須要什麼"垃圾回收器"來釋放內存的,由於當它們出了做用域後會自動釋放所佔內存(由於它們都保存在"堆棧"中,學過數據結構可知這是一種先進後出的結構);

 

2.只有引用類型的引用所指向的對象實例才保存在"堆"中,而堆由於是一個自由存儲空間,因此它並無像"堆棧"那樣有生存期("堆棧"的元素彈出後就代 表生存期結束,也就表明釋放了內存),而且很是要注意的是,"垃圾回收器"只對這塊區域起做用;

3."垃圾回收器"也許並不像許多人想象的同樣會當即執行(當堆中的資源須要釋放時),而是在引用類型的引用被刪除和它在"堆"中的對象實例被刪除中間有 個間隔,爲何呢? 由於"垃圾回收器"的調用是比較消耗系統資源的,所以不可能常常被調用;

 (固然,用戶代碼能夠用方法System.GC.Collect()來強制執行"垃圾回收器")

 

4.有析構函數的對象須要垃圾收集器兩次處理才能刪除:第一次調用析構函數時,沒有刪除對象,第二次調用才真正刪除對象;

5.因爲垃圾收集器的工做方式,沒法肯定C#對象的析構函數什麼時候執行;

6.可實現IDisposable接口的Dispose()來顯示釋放由對象使用的全部未託管資源;

7.垃圾收集器在釋放了它能釋放的全部對象後,就會壓縮其餘對象,把他們都移動回託管堆的端部,再次造成一個連續的塊。    

 

導言

       垃圾回收(Garbage Collection)在.net中是一個很重要的機制。本文將要談到CLR4.0對垃圾回收作了哪些改進。爲了更好地理解這些改進, 本文也要介紹垃圾回收的歷史。這樣咱們對整個垃圾回收有一個大的印象。這個大印象對於咱們掌握.net架構是有幫助的。

關於垃圾回收

       在C++時代,咱們須要本身來管理申請內存和釋放內存. 因而有了new, delete關鍵字. 還有的一些內存申請和釋放函數(malloc/free)。C++程序必須很好地管理本身的內存,否則就會形成內存泄漏(Memory leak)。在.net時代, 微軟爲開發人員提供了一個強有力的機制--垃圾回收,垃圾回收機制是CLR的一部分, 咱們不用操心內存什麼時候釋放,咱們能夠花更多精力關注應用程序的業務邏輯。CLR裏面的垃圾回收機制用必定的算法判斷某些內存程序再也不使用,回收這些內存並交給咱們的程序再使用.

垃圾回收的功能

用來管理託管資源和非託管資源所佔用的內存分配和釋放。

尋找再也不使用的對象,釋放其佔用的內存, 以及釋放非託管資源所佔用的內存。 

垃圾回收器釋放內存以後, 出現了內存碎片, 垃圾回收器移動一些對象,以獲得整塊的內存,同時全部的對象引用都將被調整爲指向對象新的存儲位置。

下面咱們來看看CLR是如何管理託管資源的。

託管堆和託管棧

        .net CLR在運行咱們的程序時,在內存中開闢了兩塊地方做不一樣的用處--託管棧和託管堆. 託管棧用來存放局部變量, 跟蹤程序調用與返回。託管堆用來存放引用類型。引用類型老是存放於託管堆。值類型一般是放在託管棧上面的. 若是一個值類型是一個引用類型的一部分,則此值類型隨該引用類型存放於託管堆中。哪些東西是值類型? 就是定義於System.ValueType之下的這些類型:

bool byte char decimal double enum float int long sbyte short struct uint ulong ushort

       什麼是引用類型呢? 只要用class, interface, delegate, object, string聲明的類型, 就是引用類型。

咱們定義一個局部變量, 其類型是引用類型。當咱們給它賦一個值,以下例:

private void MyMethod()
{
   MyType  myType =
new MyType();
   myType.DoSomeThing();
}
  在此例中, myType 是局部變量, new實例化出來的對象存儲於託管堆, 而myType變量(引用部分)存儲於託管棧。在託管棧的myType變量存儲了一個指向託管堆上new實例化出來對象的引用。CLR運行此方法時, 將託管棧指針移動, 爲局部變量myType分配空間, 當執行new時, CLR先查看託管堆是否有足夠空間, 足夠的話就只是簡單地移動下託管堆的指針,來爲MyType對象分配空間,若是託管堆沒有足夠空間,會引發垃圾收集器工做。CLR在分配空間以前,知道全部類型的元數據,因此能知道每一個類型的大小,即佔用空間的大小。

       當CLR完成MyMethod方法的執行時, 託管棧上的myType局部變量被當即刪除, 可是託管堆上的MyType對象卻不必定立刻刪除。這取決於垃圾收集器的觸發條件。後面要介紹此觸發條件。

      上面咱們瞭解了CLR如何管理託管資源。下面咱們來看垃圾收集器如何尋找再也不使用的託管對象,並釋放其佔用的內存。

垃圾收集器如何尋找再也不使用的託管對象,並釋放其佔用的內存

       前面咱們瞭解了CLR如何管理託管棧上的對象。按照先進後出原則便可比較容易地管理託管棧的內存。託管堆的管理比託管棧的管理複雜多了。下面所談都是針對託管堆的管理。

     垃圾收集器尋找再也不使用的託管對象時, 其判斷依據是當一個對象再也不有引用指向它, 就說明此對象是能夠釋放了。一些複雜的狀況下能夠出現一個對象指向第二個對象,第二個對象指向第三個對象,…就象一個鏈表。那麼,垃圾收集器從哪裏開始查找再也不使用的託管對象呢? 以剛纔所說的鏈表爲例,顯然是應該從鏈表的開頭開始查找。那麼,在鏈表開頭的是些什麼東東呢?

     是局部變量, 全局變量, 靜態變量, 指向託管堆的CPU寄存器。在CLR中,它們被稱之爲根。

有了開始點,垃圾收集器接下來怎麼作呢?

建立一個圖, 一個描述對象間引用關係的圖.

       垃圾收集器首先假定全部在託管堆裏面的對象都是不可到達的(或者說沒有被引用的,再也不須要的), 而後從根上的那些變量開始, 針對每個根上的變量,找出其引用的託管堆上的對象,將找到的對象加入這個圖, 而後再沿着這個對象往下找,看看它有沒有引用另一個對象,有的話,繼續將找到的對象加入圖中,若是沒有的話,就說明這條鏈已經找到尾部了。垃圾收集器就去從根上的另一個變量開始找,直到根上的全部變量都找過了, 而後垃圾收集器才中止查找。值得一提的是,在查找過程當中, 垃圾收集器有些小的優化,如: 因爲對象間的引用關係多是比較複雜的, 因此有可能找到一個對象, 而此對象已經加入圖了, 那麼垃圾收集器就再也不在此條鏈上繼續查找, 轉去其餘的鏈上繼續找。這樣對垃圾收集器的性能有所改善。 

    

 內存釋放和壓縮
       建立對象引用圖以後,垃圾回收器將那些沒有在這個圖中的對象(即再也不須要的對象)釋放。釋放內存以後, 出現了內存碎片, 垃圾回收器掃描託管堆,找到連續的內存塊,而後移動未回收的對象到更低的地址, 以獲得整塊的內存,同時全部的對象引用都將被調整爲指向對象新的存儲位置。這就象一個夯實的動做。也就是說,一個對象即便沒有被清除,因爲內存壓縮,致使他的引用位置發生變化。

      下面要說到的是代的概念。代概念的引入是爲了提升垃圾收集器的總體性能。

代 Gen

  請想想若是垃圾收集器每次老是掃描全部託管堆中的對象,對性能會有什麼影響。會不會很慢?是的。微軟所以引入了代的概念。

  爲何代的概念能夠提升垃圾收集器的性能?由於微軟是基於對大量編程實踐的科學估計,作了一些假定而這些假定符合絕大多數的編程實踐:

  • 越新的對象,其生命週期越短。

  • 越老的對象,其生命周越長。

  • 新對象之間一般有強的關係並被同時訪問。

  • 壓縮一部分堆比壓縮整個堆快。

  有了代的概念,垃圾回收活動就能夠大部分侷限於一個較小的區域來進行。這樣就對垃圾回收的性能有所提升。

讓咱們來看垃圾收集器具體是怎麼實現代的:

第0代:新建對象和從未通過垃圾回收對象的集合  

第1代:在第0代收集活動中未回收的對象集合 

第2代:在第1和第2代中未回收的對象集合, 即垃圾收集器最高只支持到第2代, 若是某個對象在第2代的回收活動中留下來,它仍呆在第2代的內存中。

  當程序剛開始運行,垃圾收集器分配爲每一代分配了必定的內存,這些內存的初始大小由.net framework的策略決定。垃圾收集器記錄了這三代的內存起始地址和大小。這三代的內存是鏈接在一塊兒的。第2代的內存在第1代內存之下,第1代內存在第0代內存之下。應用程序分配新的託管對象老是從第0代中分配。若是第0代中內存足夠,CLR就很簡單快速地移動一下指針,完成內存的分配。這是很快速的。當第0代內存不足以容納新的對象時,就觸發垃圾收集器工做,來回收第0代中再也不須要的對象,當回收完畢,垃圾收集器就夯實第0代中沒有回收的對象至低的地址,同時移動指針至空閒空間的開始地址(同時按照移動後的地址去更新那些相關引用),此時第0代就空了,由於那些在第0代中沒有回收的對象都移到了第1代。

  當只對第0代進行收集時,所發生的就是部分收集。這與以前所說的所有收集有所區別(由於代的引入)。對第0代收集時,一樣是從根開始找那些正引用的對象,但接下來的步驟有所不一樣。當垃圾收集器找到一個指向第1代或者第2代地址的根,垃圾收集器就忽略此根,繼續找其餘根,若是找到一個指向第0代對象的根,就將此對象加入圖。這樣就能夠只處理第0代內存中的垃圾。這樣作有個先決條件,就是應用程序此前沒有去寫第1代和第2代的內存,沒有讓第1代或者第2代中某個對象指向第0代的內存。可是實際中應用程序是有可能寫第1代或者第2代的內存的。針對這種狀況,CLR有專門的數據結構(Card table)來標誌應用程序是否曾經寫第1代或者第2代的內存。若是在這次對第0代進行收集以前,應用程序寫過第1代或者第2代的內存,那些被Card Table登記的對象(在第1代或者第2代)將也要在這次對第0代收集時做爲根。這樣,才能夠正確地對第0代進行收集。

       以上說到了第0代收集發生的一個條件,即第0代沒有足夠內存去容納新對象。執行GC.Collect()也會觸發對第0代的收集。另外,垃圾收集器還爲每一代都維護着一個監視閥值。第0代內存達到這個第0代的閥值時也會觸發對第0代的收集。對第1代的收集發生在執行GC.Collect(1)或者第1代內存達到第1代的閥值時。第2代也有相似的觸發條件。當第1代收集時,第0代也須要收集。當第2代收集時,第1和第0代也須要收集。在第n代收集以後仍然存留下來的對象將被轉移到第n+1代的內存中,若是n=2, 那麼存留下來的對象還將留在第2代中。

 

對象結束

  對象結束機制是程序員忘記用Close或者Dispose等方法清理申請的資源時的一個保證措施。以下的一個類,當一個此類的實例建立時,在第0代中分配內存,同時此對象的引用要被加入到一個由CLR維護的結束隊列中去。

 

[c-sharp] view plaincopyprint?

  1. public class BaseObj   

  2. {      

  3. public BaseObj()   

  4. {   

  5. }      

  6.  protected override void Finalize()   

  7. {          

  8. // Perform resource cleanup code here...          

  9. // Example: Close file/Close network connection        Console.WriteLine("In Finalize.");     

  10. }  

  11. }  

 

 

        當此對象成爲垃圾時,垃圾收集器將其引用從結束隊列移到待結束隊列中,同時此對象會被加入引用關係圖。一個獨立運行的CLR線程將一個個從待結束隊列(Jeffrey Richter稱之爲Freachable queue)取出對象,執行其Finalize方法以清理資源。所以,此對象不會立刻被垃圾收集器回收。只有當此對象的Finalize方法被執行完畢後,其引用纔會從待結束隊列中移除。等下一輪迴收時,垃圾回收器纔會將其回收。

       GC類有兩個公共靜態方法GC.ReRegisterForFinalize和GC.SuppressFinalize你們也許想了解一下,ReRegisterForFinalize是將指向對象的引用添加到結束隊列中(即代表此對象須要結束),SuppressFinalize是將結束隊列中該對象的引用移除,CLR將再也不會執行其Finalize方法。

        由於有Finalize方法的對象在new時就自動會加入結束隊列中,因此ReRegisterForFinalize能夠用的場合比較少。ReRegisterForFinalize比較典型的是配合重生(Resurrection)的場合來用。重生指的是在Finalize方法中讓根又從新指向此對象。那麼此對象又成了可到達的對象,不會被垃圾收集器收集,可是此對象的引用未被加入結束隊列中。因此此處須要用ReRegisterForFinalize方法來將對象的引用添加到結束隊列中。由於重生自己在現實應用中就不多見,因此ReRegisterForFinalize也將比較少用到。

       相比之下,SuppressFinalize更經常使用些。SuppressFinalize用於同時實現了Finalize方法和Dispose()方法來釋放資源的狀況下。在Dispose()方法中調用GC.SuppressFinalize(this),那麼CLR就不會執行Finalize方法。Finalize方法是程序員忘記用Close或者Dispose等方法清理資源時的一個保證措施。若是程序員記得調用Dispose(),那麼就會不執行Finalize()來再次釋放資源;若是程序員忘記調用Dispose(), Finalize方法將是最後一個保證資源釋放的措施。這樣作不失爲一種雙保險的方案。

       對象結束機制對垃圾收集器的性能影響比較大,同時CLR難以保證調用Finalize方法的時間和次序。所以,儘可能不要用對象結束機制,而採用自定義的方法或者名爲Close,Dispose的方法來清理資源。能夠考慮實現IDisposable接口併爲Dispose方法寫好清理資源的方法體。

 

大對象堆

      大對象堆專用於存放大於85000字節的對象。初始的大對象內存區域堆一般在第0代內存之上,而且與第0代內存不鄰接。第0,第1和第2代合起來稱爲小對象堆。CLR分配一個新的對象時,若是其大小小於85000字節,就在第0代中分配,若是其大小大於等於85000字節,就在大對象堆中分配。

       由於大對象的尺寸比較大,收集時成本比較高,因此對大對象的收集是在第2代收集時。大對象的收集也是從根開始查找可到達對象,那些不可到達的大對象就可回收。垃圾收集器回收了大對象後,不會對大對象堆進行夯實操做(也就是碎片整理,畢竟移動大對象成本較高),而是用一個空閒對象表的數據結構來登記哪些對象的空間能夠再利用,其中兩個相鄰的大對象回收將在空閒對象表中做爲一個對象對待。空閒對象表登記的空間將能夠再分配新的大對象。

      大對象的分配,回收的成本都較小對象高,所以在實踐中最好避免很快地分配大對象又很快回收,能夠考慮如何分配一個大對象池,重複利用這個大對象池,而不頻繁地回收。

 弱引用
       弱引用是相對強引用來講的。強引用指的是根有一個指針指向對象。弱引用是經過對強引用加以弱化而獲得的。這個弱化的手段就是用System.WeakReference類。因此精確地說,強引用指的是根有一個非WeakReference類型的指針指向對象,而弱引用就是根有一個WeakReference類型的指針指向對象。垃圾收集器看到一個WeakReference類型的根指向某個對象,就會特別處理。因此在垃圾收集器建立對象引用關係圖的時候,若是遇到一個弱引用指針,那麼垃圾收集器就不會將其加入圖中。若是一個對象只有弱引用指向它,那麼垃圾收集器能夠收集此對象。一旦將一個強引用加到對象上,無論對象有沒有弱引用,對象都不可回收。

      垃圾收集器對WeakReference類的特別處理從new操做就開始。一般的類,只要new操做,就會從託管堆分配空間,而WeakReference類的new操做不是這樣作的。咱們先來看WeakReference類的構造函數: 

WeakReference(Object target);

WeakReference(Object target, Boolean trackResurrection);

       此二構造函數都須要一個對象的引用,第二個構造函數還須要一個布爾值參數來表示咱們是否須要跟蹤對象的重生。此參數的意義後文會交代。

  假設咱們有兩個類MyClass和MyAnotherClass,都有Finalize方法。咱們聲明兩個對象: 

MyClass myObject = new MyClass();

MyAnotherClass myAnotherObject = new MyAnotherClass();

  當咱們用這樣的代碼聲明一個弱引用對象: WeakReference myShortWeakReferenceObject = new WeakReference( myObject );

       垃圾收集器內部有一個短弱引用表,用這樣聲明的弱引用對象將不會在託管堆中分配空間,而是在短弱引用表中分配一個槽。此槽中記錄對myObject的引用。New操做將此槽的地址返回給myShortWeakReferenceObject變量。

      若是咱們用這樣的代碼聲明一個弱引用對象(咱們要跟蹤該對象的重生): WeakReference myLongWeakReferenceObject = new WeakReference( myAnotherObject, true );

      垃圾收集器內部有一個長弱引用表,用這樣聲明的弱引用對象將不會在託管堆中分配空間,而是在長弱引用表中分配一個槽。此槽中記錄對myAnotherObject的引用。New操做將此槽的地址返回給myLongWeakReferenceObject變量。

垃圾收集器此時的收集流程是這樣的:

1. 垃圾收集器創建對象引用圖,來找到全部的可到達對象。前文已經說過如何創建圖。特別的地方是,若是遇到非WeakReference指針,就加入圖,若是遇到WeakReference指針,就不加入圖。這樣圖就建好了。

2. 垃圾收集器掃描短弱引用表。若是一個指針指向一個不在圖中的對象,那麼此對象就是一個不可到達的對象,垃圾收集器就將短弱引用表相應的槽置空。

3. 垃圾收集器掃描結束隊列。若是隊列中一個指針指向一個不在圖中的對象,此指針將被從結束隊列移到待結束隊列,同時此對象被加入引用關係圖中,由於此時此對象是Finalize可到達的。

4. 垃圾收集器掃描長弱引用表。若是一個指針指向一個不在圖中的對象(注意此時圖中已包含Finalize可到達的對象),那麼此對象就是一個不可到達的對象,垃圾收集器就將長弱引用表相應的槽置空。

5. 垃圾收集器夯實(壓縮)託管堆。
  短弱引用不跟蹤重生。即垃圾收集器發現一個對象爲不可到達就當即將短弱引用表相應的槽置空。若是該對象有Finalize方法,而且Finalize方法尚未執行,因此該對象就還存在。若是應用程序訪問弱引用對象的Target屬性,即便該對象還存在,也會獲得null。

   長弱引用跟蹤重生。即垃圾收集器發現一個對象是Finalize可到達的對象,就不將相應的槽置空。由於Finalize方法尚未執行,因此該對象就還存在。若是應用程序訪問弱引用對象的Target屬性,能夠獲得該對象;可是若是Finalize方法已經被執行,就代表該對象沒有重生。

   按照上面的例子,若是執行以下代碼會發生什麼呢?

 

[c-sharp] view plaincopyprint?

  1. //File: MyClass.cs  

  2. using System;  

  3. using System.Collections.Generic;  

  4. using System.Text;  

  5.   

  6. namespace ConsoleApplication2  

  7. {  

  8.     class MyClass  

  9.     {  

  10.         ~MyClass()  

  11.         {  

  12.             Console.WriteLine("In MyClass destructor+++++++++++++++++++++++++++");  

  13.         }  

  14.     }  

  15. }//File: MyAnotherClass.cs  

  16. using System;  

  17. using System.Collections.Generic;  

  18. using System.Text;  

  19.   

  20. namespace ConsoleApplication2  

  21. {  

  22.     public class MyAnotherClass  

  23.     {  

  24.         ~MyAnotherClass()  

  25.         {  

  26.             Console.WriteLine("In MyAnotherClass destructor___________________________________");  

  27.         }  

  28.     }  

  29. }//File: Program.cs  

  30. using System;  

  31. using System.Collections.Generic;  

  32. using System.Text;  

  33.   

  34. namespace ConsoleApplication2  

  35. {  

  36.     class Program  

  37.     {  

  38.         static void Main(string[] args)  

  39.         {  

  40.             MyClass myClass = new MyClass();  

  41.             MyAnotherClass myAnotherClass = new MyAnotherClass();  

  42.             WeakReference myShortWeakReferenceObject = new WeakReference(myClass);  

  43.             WeakReference myLongWeakReferenceObject = new WeakReference(myAnotherClass, true);  

  44.             Console.WriteLine("Release managed resources by setting locals to null.");  

  45.             myClass = null;  

  46.             myAnotherClass = null;  

  47.   

  48.             Console.WriteLine("Check whether the objects are still alive.");  

  49.             CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");  

  50.             CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");  

  51.   

  52.             Console.WriteLine("Programmatically cause GC.");  

  53.             GC.Collect();  

  54.   

  55.             Console.WriteLine("Wait for GC runs the finalization methods.");  

  56.             GC.WaitForPendingFinalizers();  

  57.   

  58.             //Check whether the objects are still alive.  

  59.             CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");  

  60.             CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");  

  61.   

  62.             Console.WriteLine("Programmatically cause GC again. Let's see what will happen this time.");  

  63.             GC.Collect();  

  64.   

  65.             //Check whether the objects are still alive.  

  66.             CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");  

  67.             CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");  

  68.   

  69.             myAnotherClass = (MyAnotherClass)myLongWeakReferenceObject.Target;  

  70.   

  71.             Console.ReadLine();  

  72.         }  

  73.   

  74.         static void CheckStatus(WeakReference weakObject, string strLocalVariableName, string strWeakObjectName)  

  75.         {  

  76.             Console.WriteLine(strLocalVariableName + (weakObject.IsAlive ? " is still alive." : " is not alive."));  

  77.             Console.WriteLine(strWeakObjectName + (weakObject.Target != null ? ".Target is not null." : ".Target is null."));  

  78.             Console.WriteLine();  

  79.         }  

  80.     }  

  81. }  

 

  請你們想想若是MyAnotherClass類沒有Finalize方法呢?

  或者:若是咱們註釋掉這行: GC.WaitForPendingFinalizers();試着多執行此代碼屢次, 看看每次會輸出什麼呢?是否是Finzalization方法被執行的時機不肯定?

       弱引用是爲大對象準備的。在實際當中,若是不用弱引用,只用強引用,則用過了該大對象,而後將強引用置null,讓GC能夠回收它,可是沒過多久咱們又須要這個大對象了,可是已經沒有辦法找回原來的對象,只好從新建立實例,這樣就浪費了建立實例所需的計算資源;而若是不置null,就會佔用不少內存資源。對於這種狀況,咱們能夠建立一個這個大對象的弱引用,這樣在內存不夠時將強引用置null,讓GC能夠回收,而在沒有被GC回收前,若是咱們短期內還須要該大對象,咱們還能夠再次找回該對象,不用再從新建立實例。是否是節省了一些開銷?

 

垃圾收集的通常流程

如下是垃圾收集的通常流程,受應用場景(如服務器應用,併發和非併發)影響,具體的垃圾回收流程可能有所不一樣。

1. 掛起.net應用的全部線程

2. 找到可回收的對象

3. 回收可回收的對象並壓縮託管堆

4. 繼續.net應用的全部線程

垃圾收集的模式

工做站模式(workstation mode) 

       這種模式下GC假設機器上運行的其餘應用程序對CPU資源要求不高,而且該模式可使用併發或非併發的模式運行。 

       併發模式是默認的工做站模式,該模式下垃圾回收器分配一個額外的後臺線程在應用程序運行時併發回收對象。一個線程由於分配對象形成第0代超出預算時,垃圾回收器掛起全部線程,判斷須要回收哪些代,若是須要回收第2代,就會增長第0代的大小。而後應用程序恢復執行。併發回收是爲了給用戶更好的交互體驗,適合客戶端應用程序,可是同時要注意併發回收對性能有損害,使用更多地內存。 

禁用併發模式的配置爲 

<configuration>
    <runtime>
        <gcConcurrentenabled="false"/>
    </runtime>
< /configuration>

服務器模式 (servermode)

        這種模式下GC假設機器上沒有運行其餘應用程序,全部的CPU均可以用來進行垃圾回收操做。在這種狀況下虛擬內存按照CPU數量劃分區域分開對待,每一個CPU上都運行一個GC線程負責回收本身的區域。 

相關文章
相關標籤/搜索