深入理解.NET內存回收機制

發表于:2007-05-25來源:作者:點擊數: 標簽:.NET深入回收內存機制
[前言:] .Net平臺提供了許多新功能,這些功能能夠幫助 程序員 生產出更高效和穩定的代碼。其中之一就是垃圾回收器(GC)。這篇文章將深入探討這一功能,了解它是如何工作的以及如何編寫代碼來更好地使用這一.Net平臺提供的功能。 .Net中的內存回收機制 垃
  [前言:].Net平臺提供了許多新功能,這些功能能夠幫助程序員生產出更高效和穩定的代碼。其中之一就是垃圾回收器(GC)。這篇文章將深入探討這一功能,了解它是如何工作的以及如何編寫代碼來更好地使用這一.Net平臺提供的功能。

  .Net中的內存回收機制

  垃圾回收器是用來管理應用程序的內存分配和釋放的。在垃圾回收器出現以前,程序員在使用內存時需要向系統申請內存空間。有些語言,例如Visual Basic,可以自動完成向系統申請內存空間的工作。但是在諸如Visual C++的語言中要求程序員在程序代碼中申請內存空間。如果程序員在使用了內存之后忘了釋放內存,則會引起內存泄漏。但是有了垃圾回收器,程序員就不必關心內存中對象在離開生存期后是否被釋放的問題。當一個應用程序在運行的時候,垃圾回收器設置了一個托管堆。托管堆和C語言中的堆向類似,但是程序員不需要從托管堆中釋放對象,并且在托管堆中對象的存放是連續的。

  每次當開發人員使用 new 運算符創建對象時,運行庫都從托管堆為該對象分配內存。新創建的對象被放在上次創建的對象之后。垃圾回收器保存了一個指針,該指針總是指向托管堆中最后一個對象之后的內存空間。當新的對象被產生時,運行庫就知道應該將新的對象放在內存的什么地方。同時開發人員應該將相同類型的對象放在一起。例如當開發人員希望向數據庫寫入數據的時侯,首先需要創建一個連接對象,然后是Command對象,最后是DataSet對象。如果這些對象放在托管堆相鄰的區域內,存取它們就非???。

  當垃圾回收器的指針指向托管堆以外的內存空間時,就需要回收內存中的垃圾了。在這個過程中,垃圾回收器首先假設在托管堆中所有的對象都需要被回收。然后它在托管堆中尋找被根對象引用的對象(根對象就是全局,靜態或處于活動中的局部變量以及寄存器指向的對象),找到后將它們加入一個有效對象的列表中,并在已經搜索過的對象中尋找是否有對象被新加入的有效對象引用。直到垃圾回收器檢查完所有的對象后,就有一份根對象和根對象直接或間接引用了的對象的列表,而其它沒有在表中的對象就被從內存中回收。

  當對象被加入到托管堆中時,如果它實現了finalize()方法,垃圾回收器會在它的終結列表(Finalization List)中加入一個指向該對象的指針。當該對象被回收時,垃圾回收器會檢查終結列表,看是否需要調用對象的finalize()方法。如果有的話,垃圾回收器將指向該對象的指針加入一個完成器隊列中,該完成器隊列保存了那些準備調用finalize()方法的對象。到了這一步對象還不是真正的垃圾對象。因此垃圾回收器還沒有把他們從托管堆中回收。

  當對象準備被終結時,另一個垃圾回收器線程會調用在完成器隊列中每個對象的finalize()方法。當調用完成后,線程將指針從完成器隊列中移出,這樣垃圾回收器就知道在下一次回收對象時可以清除被終結的對象了。從上面可以看到垃圾回收機制帶來的很大一部分額外工作就是調用finalize()方法,因此在實際編程中開發人員應該避免在類中實現finalize()方法。

  對于finalize()方法的另一個問題是開發人員不知道什么時候它將被調用。它不像C++中的析構函數在刪除一個對象時被調用。為了解決這個問題,在.Net中提供了一個接口IDisposable。微軟建議在實現帶有fianlize()方法的類的時侯按照下面的模式定義對象:


public class Class1 : IDisposable
{
 public Class1()
 {
 }

 ~Class1 ()
 {
  //垃圾回收器將調用該方法,因此參數需要為false。
  Dispose (false);
 }

 //該方法定義在IDisposable接口中。
 public void Dispose ()
 {
  //該方法由程序調用,在調用該方法之后對象將被終結。
  //因為我們不希望垃圾回收器再次終結對象,因此需要從終結列表中去除該對象。
  GC.SuppressFinalize (this);
  //因為是由程序調用該方法的,因此參數為true。
  Dispose (true);
 }

 //所有與回收相關的工作都由該方法完成
 private void Dispose(bool disposing)
  {
  lock(this) //避免產生線程錯誤。
  {
   if (disposing)
   {
    //需要程序員完成釋放對象占用的資源。
   }

  //對象將被垃圾回收器終結。在這里添加其它和清除對象相關的代碼。
 }
}
}

  現在我們了解了垃圾回收器工作的基本原理,接下來讓我們看一看垃圾回收器內部是如何工作的。目前有很多種類型的垃圾回收器。微軟實現了一種生存期垃圾回收器(Generational Garbage Collector)。生存期垃圾回收器將內存分為很多個托管堆,每一個托管堆對應一種生存期等級。生存期垃圾回收器遵循著下面的原則:

  新生成的對象,其生存期越短;而對象生成時間越長的對象,其生存期也就越長。對于垃圾回收器來說,回收一部分對象總是比回收全部對象要快,因此垃圾回收器對于那些生存期短的對象回收的頻率要比生存期長的對象的回收頻率高。

  .Net中的垃圾回收器中目前有三個生存期等級:0,1和2。0、1、2等級對應的托管堆的初始化大小分別是256K,2M和10M。垃圾回收器在發現改變大小能夠提高性能的話,會改變托管堆的大小。例如當應用程序初始化了許多小的對象,并且這些對象會被很快回收的話,垃圾回收器就會將0等級的托管堆變為128K,并且提高回收的頻率。如果情況相反,垃圾回收器發現在0等級的托管堆中不能回收很多空間時,就會增加托管堆的大小。



  在應用程序初始化的之前,所有等級的托管堆都是空的。當對象被初始化的時候,他們會按照初始化的先后順序被放入等級為0的托管堆中。在托管堆中對象的存放是連續的,這樣使得托管堆存取對象的速度很快,因為托管對不必對內存進行搜索。垃圾回收器中保存了一個指針指向托管堆中最后一個對象之后的內存空間。圖一中顯示了一個包含四個對象的0等級的托管堆。


圖一 包含四個對象的托管堆

  當0等級托管堆被對象填滿后,例如候程序初始化了新的對象,使0等級托管堆的大小超過了256K,垃圾回收器會檢查托管堆中的所有對象,看是否有對象可以回收。當開始回收操作時,如前面提到的,垃圾回收器會找出根節點和根節點直接或間接引用了的對象,然后將這些對象轉移到1等級托管堆中,并將0等級托管堆的指針移到最開始的位置以清除所有的對象。同時垃圾回收器會壓縮1等級托管堆以保證所有對象之間沒有內存空隙。當1等級托管堆滿了之后,會將對象轉移到2等級的托管堆。

  例如在圖一之后,垃圾回收器開始回收對象,假定D對象將被回收,同時程序創建了E和F對象。這時候托管堆中的對象如圖二所示。


圖二 回收對象后的0等級和1等級托管堆

  然后程序創建了新的對象G和H,再一次觸發了垃圾回收器。對象E將被回收。這時候托管堆中的對象如圖三所示。



  生存期垃圾回收器的原則也有例外的情況。當對象的大小超過84K時,對象會被放入"大對象區"。大對象區中的對象不會被垃圾回收器回收,也不會被壓縮。這樣做是為了強制垃圾回收器只能回收小對象以提高程序的性能。

  控制垃圾回收器

  在.Net框架中提供了很多方法使開發人員能夠直接控制垃圾回收器的行為。通過使用GC.Collect()或GC.Collect(int GenerationNumber)開發人員可以強制垃圾回收器對所有等級的托管堆進行回收操作。在大多數的情況下開發人員不需要干涉垃圾回收器的行為,但是有些情況下,例如當程序進行了非常復雜的操作后希望確認內存中的垃圾對象已經被回收,就可以使用上面的方法。另一個方法是GC.WaitForPendingFinalizers(),它可以掛起當前線程,直到處理完成器隊列的線程清空該隊列為止。

  使用垃圾回收器最好的方法就是跟蹤程序中定義的對象,在程序不需要它們的時候手動釋放它們。例如程序中的一個對象中有一個字符串屬性,該屬性會占用一定的內存空間。當該屬性不再被使用時,開發人員可以在程序中將其設定為null,這樣垃圾回收器就可以回收該字符串占用的空間。另外,如果開發人員確定不再使用某個對象時,需要同時確定沒有其它對象引用該對象,否則垃圾回收器不會回收該對象。

  另外值得一提的是finalize()方法應該在較短的時間內完成,這是因為垃圾回收器給finalize()方法限定了一個時間,如果finalize()方法在規定時間內還沒有完成,垃圾回收器會終止運行finalize()方法的線程。在下面這些情況下程序會調用對象的finalize()方法:

   0等級垃圾回收器已滿

   程序調用了執行垃圾回收的方法

   公共語言運行庫正在卸載一個應用程序域

   公共語言運行庫正在被卸載

原文轉自:http://www.anti-gravitydesign.com

評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97