程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> c#的內存管理(托管及未托管對象管理)

c#的內存管理(托管及未托管對象管理)

編輯:關於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;

 

// 顯示調用的Dispose方法

  public void Dispose()

{

Dispose(true);

GC.SuppressFinalize(this);

}

// 實際的清除方法

  protected virtual void Dispose(bool disposing)

{

if (!isDisposed)

{

  if (disposing)

   {

// 這裡執行清除托管對象的操作.

}

// 這裡執行清除非托管對象的操作

}

 

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

 

注:這些內容都是在網上搜的,全是引用他人的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved