對於託管資源和非託管資源的理解

對於託管資源和非託管資源的理解

 

      在.net 編程環境中,系統的資源分爲託管資源和非託管資源。html

  對於託管的資源的回收工做,是不須要人工干預回收的,並且你也沒法干預他們的回收,所可以作的只是瞭解.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%的資源都是託管資源。app

非託管資源如何釋放,.NET Framework 提供 Object.Finalize 方法,它容許對象在垃圾回收器回收該對象使用的內存時適當清理其非託管資源。默認狀況下,Finalize 方法不執行任何操做。默認狀況下,Finalize 方法不執行任何操做。若是您要讓垃圾回收器在回收對象的內存以前對對象執行清理操做,您必須在類中重寫 Finalize 方法。然而你們均可以發如今實際的編程中根本沒法override方法Finalize(),在C#中,能夠經過析構函數自動生成 Finalize 方法和對基類的Finalize 方法的調用。ide

例如:函數

複製代碼
~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方法)的不能夠訪問的內存post

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

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#提供的自動回收機制,也須要程序員使用更靈活的辦法!兩者合一既能讓程序運行飛快,也讓系統更加穩定!

相關文章
相關標籤/搜索