C#垃圾回收機制-GC
收集部落格資料排版而成,非原創
C#資源型別
簡單來說分為值型別和引用型別。前者是分配在棧上,並不需要GC回收;後者是分配在堆上,因此它的記憶體釋放和回收需要通過GC來完成
- 託管資源
- 記憶體分配的資源
- 非託管資源
- Stream,資料庫的連線,GDI+的相關物件,還有Com物件等
回收的複雜性
實際上物件有一個重要的特點導致無用物件判斷的複雜性:物件間的相互引用!
如果沒有相互引用,就可以通過“引用計數”這種簡單高效的方式實現無用物件的判斷,並實現實時回收。
正是由於相互引用的存在導致GC需要設計更為複雜的演算法,這樣帶來的最大問題在於喪失了資源回收的實時性,而變成一種不確定的方式。
回收方法
.Net提供了三種方法,也是最常見的三種,大致如下:
- 解構函式,用於GC
- 繼承IDisposable介面,實現Dispose方法;
- 提供Close方法。
Close與Dispose這兩種方法的區別在於,
呼叫完了物件的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的概念,原理如下:
- 第一次回收之前建立的物件屬於Generation 0,之後,每次回收時這個Generation的號碼就會向後挪一,也就是說,第二次回收時原來的Generation 0變成了Generation 1,而在第一次回收後和第二次回收前建立的物件將屬於Generation 0。
- GC會先試著在屬於Generation 0的物件中回收,因為這些是最新的,所以最有可能會被回收,比如一些函式中的區域性變數在退出函式時就沒有引用了(可被回收)。
- 如果在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
}