1. 程式人生 > >C#垃圾回收與記憶體管理【轉】

C#垃圾回收與記憶體管理【轉】

你清楚.Net的垃圾回收機制嗎?你能簡述一下GC的工作原理嗎?怎麼樣才能有效的管理記憶體呢?Using語句體內例項化的物件有什麼作用?

     本節的組織如下,1..Net的型別和記憶體分配2.GC垃圾收集器的工作原理3.什麼是非託管資源4.如何有效釋放物件資源。總結.現在開始我們本節的學習。

    1..Net的型別和記憶體分配

     Net中的所有型別都是(直接或間接)從 System.Object 型別派生的。

CTS 中的型別被分成兩大類——引用型別( reference type ,又叫託管型別 [managed type] ),分配在記憶體堆上,值型別(

value type )。值型別分配在堆疊上。如圖

   

     值型別在棧裡,先進後出,值型別變數的生命有先後順序,這個確保了值型別變數在推出作用域以前會釋放資源。比引用型別更簡單和高效。堆疊是從高地址往低地址分配記憶體。

     引用型別分配在託管堆(Managed Heap)上,宣告一個變數在棧上儲存,當使用new建立物件時,會把物件的地址儲存在這個變數裡。託管堆相反,從低地址往高地址分配記憶體,如圖

  

    2.GC垃圾收集器的工作原理

      上圖中,當dataSet使用過期以後,我們不顯示銷燬物件,堆上的物件還繼續存在,等待GC的 回收。

垃圾收集器通過分代支援物件的年齡化是推薦的但不是必需的。一代在記憶體裡是一個具有相對年齡的物件的單位。物件的

代號或年齡標識物件屬於那個分代。在應用程式的生命週期裡,越近建立的物件屬於越新的代,並且比早建立的物件具有

較低的分代號。最近分代裡的物件代號是0.

      在new物件時,要先搜尋空閒連結串列,找到最適合記憶體塊,分配,調整記憶體塊連結串列,合併碎片。new操作幾乎可以在O(1)的時間完成,把堆頂指標加1。工作原理是: 當託管堆上剩餘空間不足,或者Generator 0 的空間已滿的時候GC執行,開始回收記憶體。垃圾回收的開始,GC對堆記憶體的壓縮調整,物件集中到頂部。GC在掃描垃圾的時候會佔用一定的CPU時間片的,最初的GC演算法真的是掃描整個堆,效率低。現在的GC把堆中的物件分成3代,最

進入堆的是第0代(generation 0), 其次是generation 1, generation2. 第一次GC只掃描第0代。如果回收的空間足夠當前使用就不必掃描其它generation的物件。所以,GC建立物件的效率比C++高效,不需要掃描全部堆空間。它通過掃描策略,再加上記憶體管理策略帶來的效能提升,足以補償GC所佔用的CPU時間。

    3.什麼是非託管資源

  常見的非託管資源就是包裝作業系統資源的物件,例如檔案,視窗或網路連線,對於這類資源雖然垃圾回收器可以跟蹤封裝非託管資源的物件的生存期,但它知道如何清理這些資源。好在.net Framework提供的Finalize()方法,它允許在垃圾回收器回收該類資源前,適當的清理非託管資源。這裡列舉幾種常見的非託管資源:畫筆、流物件、元件物件等等資源(Object,OdbcDataReader,OleDBDataReader,Pen,Regex,Socket,StreamWriter,ApplicationContext,Brush,

Component,ComponentDesigner,Container,Context,Cursor,FileStream,

Font,Icon,Image,Matrix,Timer,Tooltip)。(參考MSDN)

    4.如何有效釋放非託管資源。

     GC無法管理非託管資源,那麼如何釋放非託管資源呢?.Net提供了兩種方式:

(1)解構函式:垃圾收集器回收非託管物件的資源時,會呼叫物件的終結方法Finalize(),進行資源的清理工作,但是由於GC工作規則的限制,GC呼叫物件的Finalize方法,第一次不會釋放資源,第二次呼叫之後才刪除物件。

(2)繼承IDisposable 介面,實現Dispose()方法,IDisposable介面定義了一個模式(具有語言級的支援),為釋放未託管的資源提供了確定的機制,並避免產生解構函式固有的與垃圾收集器相關的問題。

   為了更好的理解垃圾回收機制,我特地寫了部分程式碼,裡面添加了詳細的註釋。定義單個類FrankClassWithDispose(繼承介面IDisposableFrankClassNoFinalize(沒終結器)FrankClassWithDestructor(定義了解構函式)。

具體程式碼如下:


 1 using  System;
 2 using  System.Collections.Generic;
 3 using  System.Text;
 4 using  System.Data;
 5 using  System.Data.Odbc;
 6 using  System.Drawing;
 7 // Coded By Frank Xu Lei 18/2/2009
 8 // Study the .NET Memory Management
 9 // Garbage Collector 垃圾收集器。可以根據策略在需要的時候回收託管資源,
10 // 但是GC不知道如何管理非託管資源。如網路連線、資料庫連線、畫筆、元件等
11 // 兩個機制來解決非託管資源的釋放問題。解構函式、IDispose介面
12 // COM引用計數
13 // C++手動管理,New Delete
14 // VB自動管理 15 namespace  MemoryManagement
16 {
17      // 繼承介面IDisposable,實現Dispose方法,可以釋放FrankClassDispose的例項資源 18 public class  FrankClassWithDispose : IDisposable
19      {
20          private  OdbcConnection _odbcConnection  = null ;
21         
22          // 建構函式 23 public  FrankClassWithDispose()
24          {
25              if  (_odbcConnection  == null )
26                 _odbcConnection  = new  OdbcConnection();
27             Console.WriteLine( " FrankClassWithDispose has been created  " );
28         }
29          // 測試方法 30 public void  DoSomething()
31          {
32
33              /// /code here to do something
34 return  ;
35         }
36          // 實現Dispose,釋放本類使用的資源 37 public void  Dispose()
38          {
39              if  (_odbcConnection  != null )
40                 _odbcConnection.Dispose();
41             Console.WriteLine( " FrankClassWithDispose has been disposed " );
42         }
43     }
44      // 沒有實現Finalize,等著GC回收FrankClassFinalize的例項資源,GC執行時候直接回收 45 public class  FrankClassNoFinalize
46      {
47          private  OdbcConnection _odbcConnection  = null ;
48          // 建構函式 49 public  FrankClassNoFinalize()
50          {
51              if  (_odbcConnection  == null )
52                 _odbcConnection  = new  OdbcConnection();
53             Console.WriteLine( " FrankClassNoFinalize  has been created " );
54         }
55          // 測試方法 56 public void  DoSomething()
57          {
58
59              // GC.Collect(); 60 /// /code here to do something
61 return  ;
62         }
63     }
64      // 實現解構函式,編譯為Finalize方法,呼叫物件的解構函式
65      // GC執行時,兩次呼叫,第一次沒釋放資源,第二次才釋放
66      // FrankClassDestructor的例項資源
67      // CLR使用獨立的執行緒來執行物件的Finalize方法,頻繁呼叫會使效能下降 68 public class  FrankClassWithDestructor
69      {
70          private  OdbcConnection _odbcConnection  = null ;
71          // 建構函式 72 public  FrankClassWithDestructor()
73          {
74              if  (_odbcConnection  == null )
75                 _odbcConnection  = new  OdbcConnection();
76             Console.WriteLine( " FrankClassWithDestructor  has been created " );
77         }
78          // 測試方法 79 public void  DoSomething()
80          {
81              /// /code here to do something
82 83              return  ;
84         }
85          // 解構函式,釋放未託管資源 86 ~ FrankClassWithDestructor()
87          {
88              if  (_odbcConnection  != null )
89                 _odbcConnection.Dispose();
90             Console.WriteLine( " FrankClassWithDestructor  has been disposed " );
91         }
92     }
93 }
94

其中使用了非託管的物件OdbcConnection 的例項。建立的客戶端進行了簡單的測試。客戶端程式碼如下:


 1 using  System;
 2 using  System.Collections.Generic;
 3 using  System.Text;
 4 using  System.Data;
 5 using  MemoryManagement;
 6 // Coded By Frank Xu Lei 18/2/2009
 7 // Study the .NET Memory Management
 8 // Test The Unmanaged Objects Reclaimed.
 9 // 針對非託管程式碼的測試,比較
10 // 託管程式碼,GC可以更具策略自己回收,也可以實現IDisposable,呼叫Dispose()方法,主動釋放。 11 namespace  MemoryManagementClient
12 {
13      class  Program
14