1. 程式人生 > 實用技巧 >C#垃圾回收機制-GC

C#垃圾回收機制-GC

收集部落格資料排版而成,非原創

C#資源型別

簡單來說分為值型別和引用型別。前者是分配在棧上,並不需要GC回收;後者是分配在堆上,因此它的記憶體釋放和回收需要通過GC來完成

  • 託管資源
    • 記憶體分配的資源
  • 非託管資源
    • Stream,資料庫的連線,GDI+的相關物件,還有Com物件等

回收的複雜性

實際上物件有一個重要的特點導致無用物件判斷的複雜性:物件間的相互引用!

如果沒有相互引用,就可以通過“引用計數”這種簡單高效的方式實現無用物件的判斷,並實現實時回收。

正是由於相互引用的存在導致GC需要設計更為複雜的演算法,這樣帶來的最大問題在於喪失了資源回收的實時性,而變成一種不確定的方式。

回收方法

.Net提供了三種方法,也是最常見的三種,大致如下:

  1. 解構函式,用於GC
  2. 繼承IDisposable介面,實現Dispose方法;
  3. 提供Close方法。

CloseDispose這兩種方法的區別在於,

呼叫完了物件的Close方法後,此物件有可能被重新進行使用;

而Dispose方法來說,此物件所佔有的資源需要被標記為無用了,也就是此物件被銷燬了,不能再被使用。

例如,常見SqlConnection這個類,當呼叫完Close方法後,可以通過Open重新開啟資料庫連線,當徹底不用這個物件了就可以呼叫Dispose方法來標記此物件無用,等待GC回收

  • 對於託管資源

    .Net所指的託管僅僅針對記憶體(託管資源)的資源託管,系統提供GC-Garbage Collector機制

    ,而至於其他資源則需要手動進行釋放。

    只要判定此物件或者其包含的子物件沒有任何引用是有效的,那麼系統就認為它是垃圾。

  • 對於非託管資源的釋放,C#提供了兩種方式:

    Finalizer:寫法貌似C++的解構函式,本質上卻相差甚遠。

    • Finalizer是物件被GC回收之前呼叫的終結器,初衷是在這裡釋放非託管資源,但由於GC執行時機的不確定性,通常會導致非託管資源釋放不及時。
    • 另外,Finalizer可能還會有意想不到的副作用,比如:被回收的物件已經沒有被其他可用物件所引用,但Finalizer內部卻把它重新變成可用,這就破壞了GC垃圾收集過程的原子性,增大了GC開銷。

    Dispose Pattern:C#提供using關鍵字支援Dispose Pattern進行資源釋放。

    • 這樣能通過確定的方式釋放非託管資源,而且using結構提供了異常安全性。
    • 所以,一般建議採用Dispose Pattern,並在Finalizer中輔以檢查,如果忘記顯式Dispose物件則在Finalizer中釋放資源。

回收的過程

系統為GC安排了獨立的執行緒。那麼GC的工作大致是,查詢記憶體中物件是否成為垃圾,然後對垃圾進行釋放和回收。對於GC對於記憶體回收採取了一定的優先演算法進行輪循回收記憶體資源。

其次,對於記憶體中的垃圾分為兩種,一種是需要呼叫物件的解構函式,另一種是不需要呼叫的。GC對於前者的回收需要通過兩步完成,第一步是呼叫物件的解構函式,第二步是回收記憶體,但是要注意這兩步不是在GC一次輪循完成,即需要兩次輪循;相對於後者,則只是回收記憶體而已。

GC為了提高回收的效率使用了Generation的概念,原理如下:

  1. 第一次回收之前建立的物件屬於Generation 0,之後,每次回收時這個Generation的號碼就會向後挪一,也就是說,第二次回收時原來的Generation 0變成了Generation 1,而在第一次回收後和第二次回收前建立的物件將屬於Generation 0。
  2. GC會先試著在屬於Generation 0的物件中回收,因為這些是最新的,所以最有可能會被回收,比如一些函式中的區域性變數在退出函式時就沒有引用了(可被回收)。
  3. 如果在Generation 0中回收了足夠的記憶體,那麼GC就不會再接著回收瞭如果回收的還不夠,那麼GC就試著在Generation 1裡回收記憶體,以此類推

所以GC回收記憶體的機制不是即時回收,是記憶體中存在一定數量的垃圾之後,GC將進行記憶體回收,直到回收到足夠數量記憶體為止

補充

託管資源可以通過呼叫GC.Collect();來強制GC進行垃圾回收

非託管資源

public class AA:IDisposable//繼承IDisposable,從而獲得介面Dispose
{
	FileStream fs = new FileStream("D://a.txt",FileMode.Open);
	~AA()
	{
		MessageBox.Show("解構函式被執行了");
	}

 	#region IDisposable 成員
	public void Dispose()
	{
		fs.Dispose();
		MessageBox.Show("dispose執行了");
		GC.SuppressFinalize(this);//告訴GC,讓它不用再呼叫物件的解構函式,
	}
	#endregion
}