記憶體回收GC:.Net自動記憶體管理 上(三)終結器

來源:互聯網
上載者:User

記憶體回收GC:.Net自動記憶體管理 上(三)終結器
記憶體回收GC:.Net自動記憶體管理 上(三)終結器

前言

.Net下的GC完全解決了開發人員跟蹤記憶體使用量以及控制釋放記憶體的窘態。然而,你或午想要理解GC是怎麼工作的。此系列文章中將會解釋記憶體資源是怎麼被合理分配及管理的,並包含非常詳細的內在演算法描述。同時,還將討論GC的記憶體清理流程及什麼時清理,怎麼樣強制清理。


終結器

GC提供了另外一個能夠給你帶來好處的功能:終結器。在一個資源被回收後,終結器允許一個優雅的清理操作。使用終結器,當GC釋放資源所佔的記憶體時,它們可以進行適當的自我清理。
      簡而言之:當GC檢測到一個對象是垃圾時,GC會調用它的Finalize()方法(如果存在的話),並且回收對象所佔記憶體。看下面的代碼:
public class BaseObj {    public BaseObj() {    }    protected override void Finalize() {        // 實現資源清理的代碼        // 比如,關閉檔案或網路連接        Console.WriteLine("In Finalize.");     }}

現在你可以建立一個此對象的執行個體:
BaseObj bo = new BaseObj();
在將來如果GC檢測到這個對象變成了垃圾,GC會去查看它是否實現了Finalize方法;如果是,就會調用此方法,最後回收此對象所佔記憶體。

     許多習慣C++的開發人員會迅速把析構器destructor和Finalize方法聯絡起來。然而,值得注意的是:它們完全不是一回事,當你試圖理解終結器時你最好忘記一切有關destructor的操作。託管對象從來沒有destructor周期。當設計一個類型時,最好避免使用Finalize方法。下面列出幾個原因:
  • 可被終結(Finalize)的對象會被提升到GC的更老一代中,這會增大記憶體壓力並阻止對象記憶體回收即使GC認為此對象為垃圾對象。另外,所有與此對象有直接或間接關係的對象也會被提升。GC中的代以及代的提升在後續文章中會介紹。
  • 可被終結(Finalize)的對象需要更長時間去分配。
  • 強制GC執行終結(Finalize)方法會明顯降低效能。因此,如果有10000個對象實現了Finalize方法,GC必須執行其10000次終結方法,很傷效能。
  • 實現終結器的對象可能引用了沒有終結器的對象,導致了那些沒有終結器的對象的生命週期的延長。實際上,你可能會想把一個類型分成兩個不同的類型:沒有引用任何其它對象的帶終結器的輕量級類型和引用了其它對象而不帶終結器的類型。
  • 你無法控制終結器方法的執行時間。因此,它可能會佔有一定的資源不釋放直到GC的下次回收。
  • 當一個程式終止時,一些對象始終可以被訪問到並且不會執行其終結器。比如,後台線程使用的對象或者程式終止(或程式域卸載)過程中建立的對象。另外,預設地,為了程式能夠終止迅速,當一個程式終止時不會調用無法被訪問到的對象的終結器。當然,所有作業系統資源都會被回收利用,但是在託管堆中的對象是不能夠被恰當清理的。如果你想改變這個預設行為,你可以調用System.GC的RequestFinalizeOnShutdown方法。不過,你一定要小心地使用此方法,因為調用此方法意味著你的這個類型正在控制整個應用程式的策略。
  • 程式運行時無法保證終節器的執行順序。比如,一個對象包含一個指標指向一個內部對象,GC檢到這兩個對象都是垃圾。更進一步說,內部對象的終結器先被調用。現在,外部對象的終結器能夠訪問到內部對象並且調用其方法,但是內部對象已經被終結了。此時,結果是無法預知的。由於這個原因,強烈推薦終結器不要訪問任何內部成員對象
      如果你決定讓你的類型實現終結器,必須確保代碼可以儘可能快的執行完畢。這樣可以避免可能會阻止終結器執行的動作,包括線程同步操作。另一方面,如果你在終結器內拋出了異常,系統會認為終結器已返回(執行完畢),然後繼續執行其它對象的結終器。      當然編譯器為構造器產生代碼時,編譯器會自動地插入一個對基類構造器的調用。同樣地,當一個C++編譯器為析構器產生代碼時,編譯器會自動地插入一個對基類析構器的調用。然而,就像之前所說,終結器和析構器是不同的。編譯器對於終結器沒有特殊處理,因此編譯器不會自動產生代碼去調用基類的終結器。如果你想實現這個過程,你必須明確地在你的終結器裡調用基類終結器:
public class BaseObj {    public BaseObj() {    }    protected override void Finalize() {        Console.WriteLine("In Finalize.");         base.Finalize();    // 調用基類終結器    }}
      在衍生類型終結器中你會經常在最後一句代碼調用基類的終結器。這樣可以保持基類對象儘可能存在的更久。因為這種調用基類終結器比較常見,C#中有一個簡單的文法:
class MyObject {    ~MyObject() {        //其它代碼    }}
causes the compiler to generate this code: 
class MyObject {    protected override void Finalize() {        //其它代碼        base.Finalize();    }}


這和C++析構器有些像,但是記住C#不支援析構器。


終結器內部

表面上,終結器看起來直接了當:你建立一個帶終結器的對象,當它被回收時,終結器被調用。實際上,有更多的操作你看不到。
      
當一個應用程式建立一個新的對象,new操作符在堆中給它分配記憶體。如果有終結器,一個指向此對象的指標會被放入終結器隊列。終結器隊列是一個由GC控制的內部資料結構。隊列中的每一項指向一個對象,而此對象在記憶體回收前會調用終結器。
      中堆中存放著幾個對象。一些對象可以被程式根訪問到,一些不能。當對象C,E,F,I和J被建立,系統檢測到這些對象實現了終結器,同時在終結器隊列裡添加了指向這些對象的指標。

帶有很多個物件的託管堆(對堆與棧疑惑的可以參考:深入淺出圖解C#堆與棧):
Finalization Queue:終結器隊列;

當GC回收記憶體時,對象B,E,G,H,I和J被認為是垃圾。GC掃描終結器隊列看是否存在指標指向這些對象。如果存在,在終結器隊列中移除此指標並把它移動到終結器可達隊列。終結器可達隊列是另外一個由GC控制的內部資料結構。終結器可達隊列中的每一個指標代表一個已經執行過終結器的對象。
      
在GC回收記憶體後,託管堆變成了。你可以看到對象B,G和H所佔的記憶體已經被回收利用因為它們沒有終結器。然而,對象E,I和J所佔的記憶體沒有被回收因為它們的終結器還沒有被執行。
GC回收之後的託管堆:
Finalization Queue:終結器隊列;Freachable Queue: 終結器可達隊列

 有一個特殊運行時線程專門用於調用終結器。通常情況下,當終結器可達隊列是空隊列時,這個線程進入休眠。但是,一旦終結器可達隊列出現新的項,此線程蘇醒,移除終結器可達隊列中所有項並調用它們的終結器。由於這個機制的存在,你不可在終結器中執行任何基於此線程的編碼。比如,不要在終節器中訪問執行緒區域儲存空間。
      終結器隊列和終結器可達隊列的互動是很有趣的。Freachable中的f代表著Finalization終結器,reachable意思是對象可以被訪問。終結器可達隊列被看作類似於全域變數和靜態變數一樣的根。因此,GC會認定終結器可達隊列中所有對象都不是垃圾。
      
總而言之,當一個帶有終結器的對象不能夠被訪問到,GC認為它是垃圾。然後,GC會移動此對象在終結器隊列中的指標到終結器可達隊列中,這時此對象將不再是垃圾,同時它所佔的記憶體也不會被回收。到此為止,GC已經完成了一次垃圾掃描識別過程。GC壓縮可釋放記憶體,那個特殊運行時線程進行清理終結器可達隊列並執行每一個對象的終結器。

GC進行二次記憶體回收時,帶有終結器的垃圾對象就變成真正的垃圾,因為程式根不指向它,終結器可達隊列也不指向它。這時這個對象所佔的記憶體才會被回收。這裡我要指出的點就是帶有終結器的對象,GC需要對它們進行兩次的記憶體回收才能回收它們所佔的記憶體。實際上,GC有時需要執行兩次以上的記憶體回收因為對象可能會被提升到更老的一代中。顯示了GC二次回收後的託管堆。
GC二次回收後的託管堆:



總結終結器與GC的關係,我們需要掌握並理解。這有助於更深層次的理解記憶體回收GC機制。當然,還有比終結器更多的內容,下一節我們將介紹《複活與強制回收》。



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.