文章出處

摘自:http://blog.csdn.net/liangjin1126/article/details/2660946

適合:優化系統性能

 在.net 編程環境中,系統的資源分為托管資源和非托管資源。

  對于托管的資源的回收工作,是不需要人工干預回收的,而且你也無法干預他們的回收,所能夠做的只是了解.net CLR如何做這些操作。也就是說對于您的應用程序創建的大多數對象,可以依靠 .NET Framework 的垃圾回收器隱式地執行所有必要的內存管理任務。
    
    資源分為兩種,托管的內存資源,這是不需要我們操心的,系統已經為我們進行管理了;那么對于非托管的資源,這里再重申一下,就是Stream,數據庫的連接,GDI+的相關對象,還有Com對象等等這些資源,需要我們手動去釋放。

  對于非托管資源,您在應用程序中使用完這些非托管資源之后,必須顯示的釋放他們,例如System.IO.StreamReader的一個文件對象,必須顯示的調用對象的Close()方法關閉它,否則會占用系統的內存和資源,而且可能會出現意想不到的錯誤。

  我想說到這里,一定要清楚什么是托管資源,什么是非托管資源了?釋放。

  最常見的一類非托管資源就是包裝操作系統資源的對象,例如文件,窗口或網絡連接,對于這類資源雖然垃圾回收器可以跟蹤封裝非托管資源的對象的生存期,但它不了解具體如何清理這些資源。還好.net Framework提供了Finalize()方法,它允許在垃圾回收器回收該類資源時,適當的清理非托管資源。如果在MSDN Library 中搜索Finalize將會發現很多類似的主題,這里列舉幾種常見的非托管資源:ApplicationContext,Brush,Component,ComponentDesigner,Container,

Context,Cursor,FileStream,Font,Icon,Image,Matrix,Object,OdbcDataReader,OleDBDataReader,Pen,Regex,Socket,StreamWriter,Timer,Tooltip 等等資源。可能在使用的時候很多都沒有注意到!

    關于托管資源,就不用說了撒,像簡單的int,string,float,DateTime等等,.net中超過80%的資源都是托管資源。

非托管資源如何釋放,.NET Framework 提供 Object.Finalize 方法,它允許對象在垃圾回收器回收該對象使用的內存時適當清理其非托管資源。默認情況下,Finalize 方法不執行任何操作。默認情況下,Finalize 方法不執行任何操作。如果您要讓垃圾回收器在回收對象的內存之前對對象執行清理操作,您必須在類中重寫 Finalize 方法。然而大家都可以發現在實際的編程中根本無法override方法Finalize(),在C#中,可以通過析構函數自動生成 Finalize 方法和對基類的Finalize 方法的調用。

例如:
~MyClass()
{
  // Perform some cleanup operations here.
}
  該代碼隱式翻譯為下面的代碼。
protected override void Finalize()
{
  try
  {
    // Perform some cleanup operations here.
  }
  finally
  {
    base.Finalize();
  }
}

但是,在編程中,并不建議進行override方法Finalize(),因為,實現 Finalize 方法或析構函數對性能可能會有負面影響。一個簡單的理由如下:用 Finalize 方法回收對象使用的內存需要至少兩次垃圾回收,當垃圾回收器回收時,它只回收沒有終結器(Finalize方法)的不可訪問的內存,這時他不能回收具有終結器(Finalize方法)的不可以訪問的內存

。它改為將這些對象的項從終止隊列中移除并將他們放置在標記為“準備終止”的對象列表中,該列表中的項指向托管堆中準備被調用其終止代碼的對象,下次垃圾回收器進行回收時,就回收并釋放了這些內存。

C#托管及未托管對象管理

 C#中的對象分為值類型和引用類型,二者最大的區別在于數據的存儲方式和存儲位置. WINDOWS操作系統使用虛擬尋址系統來管理程序運行時產生的數據存放.簡單的說,該系統管理著一個內存區域,在該區域中劃撥出一部分出來專門存放值類型變量,稱為堆棧,堆棧采用先進后出的原則,將值類型變量從區域的最高地址位開始向低位地址存儲,先進后出,后進先出的管理方式保證了值類型變量在出了作用域后能即使的清除占用的內存區域,由于堆棧速度快,所保存的數據一般不太大,這部分一般不需要用戶專門操作. 值類型保存在堆棧匯總,堆棧有非常高的性能,但對于所有的變量來說還是不太靈活。通常我們希望使用一個方法分配內存,來存儲一些數據,并在方法退出后的很長一段時間內數據仍是可以使用的。只要是用new運算符來請求存儲空間,就存在這種可能性——例如所有的引用類型。此時就要使用托管堆。它在垃圾收集器的控制下工作,托管堆(或簡稱為堆)是系統管理的大內存區域中的另一個內存區域。要了解堆的工作原理和如何為引用數據類型分配內存,看看下面的代碼:
Customer arabel = new Customer();
這行代碼完成了以下操作:首先,分配堆上的內存,以存儲Customer實例(一個真正的實例,不只是一個地址)。然后把變量arabel的值設置為分配給新Customer對象的內存地址(它還調用合適的Customer()構造函數初始化類實例中的字段,但我們不必擔心這部分)。
Customer實例沒有放在堆棧中,而是放在內存的堆中。如果我們這樣操作:
Customer newaddress = arabel ;
這時候,newaddress也會保存在堆棧中,其值和arabel 相同,都是存儲Customer實例的堆地址.
     知道了這些,我們會發現這樣一個問題,如果堆棧中arabel 和newaddress兩個變量過期銷毀,那堆中保存的Customer對象會怎樣?實際上它仍保留在堆中,一直到程序停止,或垃圾收集器刪除它為止. C#的垃圾收集器如果沒有顯示調用,會定時運行并檢查內存,刪除沒有任何變量引用的數據.看起來似乎不錯,但是想想,垃圾回收器并不是時時檢查,它是定時運行,而在這段時間內如果產生大量的過期數據駐留在內存中..... 那么或許我們可以通過調用System.GC.Collect(),強迫垃圾收集器在代碼的某個地方運行,System.GC是一個表示垃圾收集器的.NET基類, Collect()方法則調用垃圾收集器。但是,這種方式適用的場合很少,(難道銷毀一個對象就讓垃圾回收檢查一便內存嗎?)例如,代碼中有大量的對象剛剛停止引用,就適合調用垃圾收集器。況且垃圾收集器的邏輯不能保證在一次垃圾收集過程中,從堆中刪除所有過期數據,對于不受垃圾回收器管理的未托管對象(例如文件句柄、網絡連接和數據庫連接),它是無能為力的。那該怎么做呢?
  這時需要制定專門的規則,確保未托管的資源在回收類的一個實例時釋放。
在定義一個類時,可以使用兩種機制來自動釋放未托管的資源。這些機制常常放在一起實現,因為每個機制都為問題提供了略為不同的解決方法。這兩個機制是:
●         聲明一個析構函數,作為類的一個成員
●         在類中實現System.IDisposable接口
下面依次討論這兩個機制,然后介紹如何同時實現它們,以獲得最佳的效果。
析構函數
前面介紹了構造函數可以指定必須在創建類的實例時進行的某些操作,在垃圾收集器刪除對象時,也可以調用析構函數。由于執行這個操作,所以析構函數初看起來似乎是放置釋放未托管資源、執行一般清理操作的代碼的最佳地方。但是,事情并不是如此簡單。由于垃圾回首器的運行規則決定了,不能在析構函數中放置需要在某一時刻運行的代碼,如果對象占用了寶貴而重要的資源,應盡可能快地釋放這些資源,此時就不能等待垃圾收集器來釋放了.
IDisposable接口
一個推薦替代析構函數的方式是使用System.IDisposable接口。IDisposable接口定義了一個模式(具有語言級的支持),為釋放未托管的資源提供了確定的機制,并避免產生析構函數固有的與垃圾函數器相關的問題。IDisposable接口聲明了一個方法Dispose(),它不帶參數,返回void,Myclass的方法Dispose()的執行代碼如下:
class Myclass : IDisposable
{
    public void Dispose()
    {
       // implementation
    }
}
Dispose()的執行代碼顯式釋放由對象直接使用的所有未托管資源,并在所有實現IDisposable接口的封裝對象上調用Dispose()。這樣,Dispose()方法在釋放未托管資源時提供了精確的控制。
假定有一個類ResourceGobbler,它使用某些外部資源,且執行IDisposable接口。如果要實例化這個類的實例,使用它,然后釋放它,就可以使用下面的代碼:
ResourceGobbler theInstance = new ResourceGobbler();

    // 這里是theInstance 對象的使用過程
  
theInstance.Dispose();
如果在處理過程中出現異常,這段代碼就沒有釋放theInstance使用的資源,所以應使用try塊,編寫下面的代碼:
ResourceGobbler theInstance = null;
try
{
    theInstance = new ResourceGobbler();
//   這里是theInstance 對象的使用過程
}
finally  
{
   if (theInstance != null) theInstance.Dispose();
}
即使在處理過程中出現了異常,這個版本也可以確保總是在theInstance上調用Dispose(),總是釋放由theInstance使用的資源。但是,如果總是要重復這樣的結構,代碼就很容易被混淆。C#提供了一種語法,可以確保在引用超出作用域時,在對象上自動調用Dispose()(但不是 Close())。該語法使用了using關鍵字來完成這一工作—— 但目前,在完全不同的環境下,它與命名空間沒有關系。下面的代碼生成與try塊相對應的IL代碼:
using (ResourceGobbler theInstance = new ResourceGobbler())
{
    //   這里是theInstance 對象的使用過程
}
using語句的后面是一對圓括號,其中是引用變量的聲明和實例化,該語句使變量放在隨附的復合語句中。另外,在變量超出作用域時,即使出現異常,也會自動調用其Dispose()方法。如果已經使用try塊來捕獲其他異常,就會比較清晰,如果避免使用using語句,僅在已有的try塊的finally 子句中調用Dispose(),還可以避免進行額外的縮進。
注意:
對于某些類來說,使用Close()要比Dispose()更富有邏輯性,例如,在處理文件或數據庫連接時,就是這樣。在這些情況下,常常實現 IDisposable接口,再執行一個獨立的Close()方法,來調用Dispose()。這種方法在類的使用上比較清晰,還支持C#提供的 using語句。

前面的章節討論了類所使用的釋放未托管資源的兩種方式:
●         利用運行庫強制執行的析構函數,但析構函數的執行是不確定的,而且,由于垃圾收集器的工作方式,它會給運行庫增加不可接受的系統開銷。
●         IDisposable接口提供了一種機制,允許類的用戶控制釋放資源的時間,但需要確保執行Dispose()。
一般情況下,最好的方法是執行這兩種機制,獲得這兩種機制的優點,克服其缺點。假定大多數程序員都能正確調用Dispose(),實現IDisposable接口,同時把析構函數作為一種安全的機制,以防沒有調用Dispose()。下面是一個雙重實現的例子:
public class ResourceHolder : IDisposable
{
     private bool isDispose = false;

   // Pointer to an external unmanaged resource.
   private IntPtr handle;

   // Other managed resource this class uses.
   private Component Components;
      
      // 顯示調用的Dispose方法
  public void Dispose()
      {
           Dispose(true);
          GC.SuppressFinalize(this);
       }

        // 實際的清除方法
  protected virtual void Dispose(bool disposing)
       {
            if (!isDisposed)
          {
               if (disposing)
           {
                     // 這里執行清除托管對象的操作.
                  }
                  // 這里執行清除非托管對象的操作
               CloseHandle(handle);
               handle = IntPtr.Zero; 
            }
    
        isDisposed=true;
      }

       // 析構函數
      ~ResourceHolder()
      {
            Dispose (false);
      }
}
可以看出,Dispose()有第二個protected重載方法,它帶一個bool參數,這是真正完成清理工作的方法。Dispose(bool)由析構函數和IDisposable.Dispose()調用。這個方式的重點是確保所有的清理代碼都放在一個地方。
傳遞給Dispose(bool)的參數表示Dispose(bool)是由析構函數調用,還是由IDisposable.Dispose()調用——Dispose(bool)不應從代碼的其他地方調用,其原因是:
●         如果客戶調用IDisposable.Dispose(),該客戶就指定應清理所有與該對象相關的資源,包括托管和非托管的資源。
●         如果調用了析構函數,在原則上,所有的資源仍需要清理。但是在這種情況下,析構函數必須由垃圾收集器調用,而且不應訪問其他托管的對象,因為我們不再能確定它們的狀態了。在這種情況下,最好清理已知的未托管資源,希望引用的托管對象還有析構函數,執行自己的清理過程。
isDispose成員變量表示對象是否已被刪除,并允許確保不多次刪除成員變量。這個簡單的方法不是線程安全的,需要調用者確保在同一時刻只有一個線程調用方法。要求客戶進行同步是一個合理的假定,在整個.NET類庫中反復使用了這個假定(例如在集合類中)。最后,IDisposable.Dispose()包含一個對System.GC. SuppressFinalize()方法的調用。SuppressFinalize()方法則告訴垃圾收集器有一個類不再需要調用其析構函數了。因為 Dispose()已經完成了所有需要的清理工作,所以析構函數不需要做任何工作。調用SuppressFinalize()就意味著垃圾收集器認為這個對象根本沒有析構函數.

  正確理解以上內容,可以大大優化系統性能,及時釋放不需要的數據,不能僅靠C#提供的自動回收機制,也需要程序員使用更靈活的辦法!二者合一既能讓程序運行飛快,也讓系統更加穩定!

 

 

 

 

 

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()