《Effective C#》讀書筆記——瞭解.NET記憶體管理機制

來源:互聯網
上載者:User

  我們知道C#是一門虛擬機器語言,在C#編譯器首先將C#代碼編譯成IL代碼,運行程式時CLR(Common Language Runtime,通用語言執行平台)通過調用JIT(just-in-time Compiler,即時編譯器)來將IL代動態即時編譯成可執行檔機器碼。在CLR中有一個非常重要的概念:CLR GC(Garbage Collector,垃圾收集器),GC自動為我們的應用程式進行記憶體管理的分配和釋放,所以在.NET應用程式中一般很少出現記憶體泄露、懸掛指標、未初始化指標等問題。同時這也是C#這樣的虛擬機器語言和C/C++等本地語言最大的不同之處。

  但是GC也不是萬能的,我們也需要自己做一些清理工作,管理那些非託管的對象,它們通常是封裝作業系統資源的對象,例如,檔案控制代碼、視窗控制代碼或網路連接

這是因為外部的資源已經超出了運行時(虛擬機器)的能力範圍,而這些非託管的資源是由作業系統來掌控的。

同時如果使用了事件處理函數和委託在對象之間建立了連結,也可能會造成對象超過你預期的長時間駐留在記憶體中同時類似於LINQ的這樣的在只有請求結果是才真正執行的查詢也會導致同樣的問題(因為查詢將把需要綁定的局部變數使用閉包封裝起來,而綁定對象只有在查詢結果離開範圍時才會被釋放掉)。可以看出在託管環境中開發應用程式,瞭解CLR記憶體的管理機制,對於我們寫出更富有效率和健壯的代碼是非常有益的。

閱讀目錄

  1.標記並壓縮演算法(Mark and Compact)

  2.清理非託管資源

      2.1 實現終結器

      2.2 實現IDisposable介面

  3.小節

  進一步閱讀和參考

 

1.標記並壓縮演算法(Mark and Compact)

   GC使用"標記並壓縮"(Mark and Compact)演算法剖析器運行對象的關係,將不可達部分對象從記憶體中清理掉。GC會從應用程式的根對象開始,通過對對象樹形結構遍曆來判定一個對象是否可達,而並不是向COM那樣跟蹤每個對象的引用。

  我們可以假設有一個EntitySet,是從資料庫中讀取得到的一系列對象的集合。每個Entity對象都可能包含對其他Entity對象的引用,還可能包含對其他Entity的串連。類似於關係型資料庫中的實體集合一樣,這些連結和引用也可能是迴圈的。每個EntitySet中都包含了一系列對象圖之間的複雜引用。但是這些並不需要我們擔心,GC會替我們很好的完成這個任務。GC通過判斷某一個對象(圖)是否為垃圾來簡化這一操作——當應用程式不需要某一個對象時,將不會保留這個對象的引用。GC會將那些沒有被任何對象之間或間接引用的對象判定為垃圾。

  GC在其專門的線程中運行,GC不但自動為程式清理不在使用的記憶體,還會在每次運行時壓縮託管堆。因此記憶體中空餘的空間會是一片連續的記憶體。

下面圖為一個託管堆在一次垃圾收集前後的狀態:

我們可以看到GC不但清理不在使用的記憶體,還會移動記憶體中的其他對象,以便壓縮正在使用的記憶體,並提高可用的記憶體空間。

 

2.清理非託管資源

  現在我們知道:託管堆上的記憶體管理是GC的職責。但是,非託管資源的記憶體管理則需要由我們開發人員自己來負責。.NET為我們提供了兩種控制非託管資源生命週期的機制:終結器(finalizer)和IDisposable介面。

 

2.1 (終結器)finalizer

  通過調用Finalize()方法通知GC回收該對象使用的記憶體時同時清理其非託管資源。終結器是一種防禦性手段,讓你的對象不管如何都可以被釋放掉其中使用的非託管資源。終結器由GC調用,調用將發生在對象成為垃圾之後的某個時間(我們並不能準確的知道其發生的具體時間,只是知道這將發生在對象不可達之後)。這和C++相比區別很大,有經驗的C++開發人員往往會在建構函式中分配關鍵資源,然後在解構函式中釋放掉。

2.1.1 使用終結器的問題

  在C#中,終結器遲早都會執行,不過其執行時間卻無法預料(也不能)。終結器僅僅能夠保證給定類型的對象所分配的非託管資源最終會被釋放,但其執行時間確實不可預料的,因此:在我們的代碼中最好避免使用終結器,也應該盡量少讓代碼邏輯使用到終結器

  依賴終結器還對帶來效能上的損失。需要執行終結器的對象會給GC帶來額外的效能開銷。當GC發現某個對象屬於垃圾,而該對象又需要執行終結器時,就不能將其直接從記憶體中移除:首先,GC將調用終結器,而終結器並不在執行垃圾收集的線程上執行。GC將把所有需要執行終結器的對象放在專門的隊列中,然後讓另一個線程來執行這些對象的終結器。用 Finalize 方法回收對象使用的記憶體需要至少兩次記憶體回收。所有我們知道:使用終結器釋放非託管資源,資來源物件不但會在記憶體中停留更長時間,GC也需要額外的線程來運行

2.1.2 GC中"代"(Generation)的概念

  .NET的GC為了最佳化其執行,引入了“代”(Generation)的概念。這概念可以讓GC更快速的找到那些更有可能是垃圾的對象。

自上一次垃圾收集以後,新建立的對象屬於第0代對象。而如果經過上一次垃圾收集之後仍舊存活的對象,將成為第1代對象。兩次以及兩次以上的垃圾收集仍沒有被銷毀的對象就變成了第2代對象。

  這樣的分代方式是為了能將局部變數和應用程式生命週期中一直使用的對象分開對待。第0代大多屬於局部變數。而成員變數和全域變數則會變成第1代對象,直至變成第2代對象。而GC通過減少檢查第1代和第2代的次數來最佳化執行過程。大概10個周期的GC中,只有一次會同時檢查第0代和第1代對象;大概100個周期的GC中,會有一次同時檢查所有對象。所有,一個需要終結器的對象可能會比普通的對象多停留9個GC周期。而對於第2代對象,甚至需要100次以上的GC周期才有機會被銷毀。

 

2.2 IDisposable介面

   相較於終結器IDisposable介面能夠以一種影響更小的方式及時的釋放非託管資源。使用IDisposable介面釋放非託管資源可以避免執行終結器給GC帶來效能上的影響。實現IDisposable介面的 Dispose 方法應該釋放它擁有的所有資源。 它還應該釋放其基底類型擁有的所有資源通過調用其父類型的 Dispose 方法。

 

小節:

  GC自動為我們的應用程式進行記憶體管理的分配和釋放,所以在.NET應用程式中一般很少出現記憶體泄露、懸掛指標、未初始化指標等問題。非託管的資源要使用終結器來保證釋放。即使終結器會對程式的效能帶來很大的影響,我們也必須提供終結器這是,這是為了保證不出現資源流失的問題。但是,使用使用IDisposable介面釋放非託管資源可以避免執行終結器給GC帶來效能上的影響。

 

參考&進一步閱讀

MSDN:自動管理記憶體

MSDN:託管執行過程

MSDN:清理非託管資源

MSDN:深入探討IDisposable

蔡學鏞:.NET的自動記憶體管理

維基百科:通用語言執行平台

《Effective C#》

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.