C# 垃圾回收GC(Garbage Collector)

在.Net裏面垃圾收集的工作方式:

運行.NET應用程序時,程序創建出來的對象實例都會被CLR跟蹤,CLR都是有記錄哪些對象還會被用到(存在引用關係);哪些對象不會再被用到(不存在引用關係)。CLR會整理不會再被用到的對象,在恰當的時機,按一定的規則銷燬部分對象,釋放出這些對象所佔用的內存。

CLR是怎麼記錄對象引用關係的?

CLR會把對象關係做成一個“樹圖”,這樣標記他們的引用關係

CLR是怎麼釋放對象的內存的?

關鍵的技術是:CLR把沒用的對象轉移到一起去,使內存連續,新分配的對象就在這塊連續的內存上創建,這樣做是爲了減少內存碎片。注意!CLR不會移動大對象

垃圾收集器按什麼規則收集垃圾對象?

CLR按對象在內存中的存活的時間長短,來收集對象。時間最短的被分配到第0代,最長的被分配到第2代,一共就3代。

一般第0貸的對象都是較小的對象,第2代的對象都是較大的對象,第0代對象GC收集時間最短(毫秒級別),第2代的對象GC收集時間最長。當程序需要內存時(或者程序空閒的時),GC會先收集第0代的對象,

收集完之後發現釋放的內存仍然不夠用,GC就會去收集第1代,第2代對象。(一般情況是按這個順序收集的)

如果GC跑過了,內存空間依然不夠用,那麼就拋出了OutOfMemoryException異常。

GC跑過幾次之後,第0代的對象仍然存在,那麼CLR會把這些對象移動到第1代,第1代的對象也是這樣。

既然有了垃圾收集器,爲什麼還要Dispose方法和析構函數?

因爲CLR的緣故,GC只能釋放託管資源,不能釋放非託管資源(數據庫鏈接、文件流等)。

那麼該如何釋放非託管資源呢?

一般我們會選擇爲類實現IDispose接口,寫一個Dispose方法。

讓調用者手動調用這個類的Dispose方法(或者用using語句塊來自動調用Dispose方法)

Dispose執行時,析構函數和垃圾收集器都還沒有開始處理這個對象的釋放工作

有時候,我們不想爲一個類型實現Dispose方法,我們想讓他自動的釋放非託管資源。那麼就要用到析構函數了。

析構函數是個很奇怪的函數,調用者無法調用對象的析構函數,析構函數是由GC調用的。

你無法預測析構函數何時會被調用,所以儘量不要在這裏操作可能被回收的託管資源,析構函數只用來釋放非託管資源

GC釋放包含析構函數的對象,比較麻煩(需要幹兩次才能幹掉她),

CLR會先讓析構函數執行,再收集它佔用的內存。

我們需要手動執行垃圾收集嗎?什麼場景下這麼做?

GC什麼時候執行垃圾收集是一個非常複雜的算法(策略)

大概可以描述成這樣:

如果GC發現上一次收集了很多對象,釋放了很大的內存,

那麼它就會盡快執行第二次回收,

如果它頻繁的回收,但釋放的內存不多,

那麼它就會減慢回收的頻率。

所以,儘量不要調用GC.Collect()這樣會破壞GC現有的執行策略。

除非你對你的應用程序內存使用情況非常瞭解,你知道何時會產生大量的垃圾,那麼你可以手動干預垃圾收集器的工作

我有一個大對象,我擔心GC要過很久纔會收集他,

【算法工作原理】

垃圾收集器的本質,就是跟蹤所有被引用到的對象,整理不再被引用的對象,回收相應的內存。

這聽起來類似於一種叫做“引用計數(Reference Counting)”的算法,然而這種算法需要遍歷所有對象,並維護它們的引

用情況,所以效率較低些,並且在出現“環引用”時很容易造成內存泄露。所以.Net中採用了一種叫做“標記與清除

(Mark Sweep)”算法來完成上述任務。


“標記與清除”算法,顧名思義,這種算法有兩個本領:


“標記”本領——垃圾的識別:從應用程序的root出發,利用相互引用關係,遍歷其在Heap上動態分配的所有對

象,沒有被引用的對象不被標記,即成爲垃圾;存活的對象被標記,即維護成了一張“根-對象可達圖”。其實,

CLR會把對象關係看做“樹圖”,無疑,瞭解數據結構的都知道,有了“樹圖”的概念,會加快遍歷對象的速度。

 

檢測並標記對象引用,是一件很有意思的事情,有很多方法可以做到,但是隻有一種是效率最優的,.Net中是利

用棧來完成的,在不斷的入棧與出棧中完成檢測:先在樹圖中選擇一個需要檢測的對象,將該對象的所有引用壓棧,

如此反覆直到棧變空爲止。棧變空意味着已經遍歷了這個局部根(或者說是樹圖中的節點)能夠到達的所有對象。樹圖

節點範圍包括局部變量(實際上局部變量會很快被回收,因爲它的作用域很明顯、很好控制)、寄存器、靜態變量,這

些元素都要重複這個操作。一旦完成,便逐個對象地檢查內存,沒有標記的對象變成了垃圾。

 

“清除”本領——回收內存:啓用Compact算法,對內存中存活的對象進行移動,修改它們的指針,使之在內存

中連續,這樣空閒的內存也就連續了,這就解決了內存碎片問題,當再次爲新對象分配內存時,CLR不必在充滿碎片

的內存中尋找適合新對象的內存空間,所以分配速度會大大提高。但是大對象(large object heap)除外,GC不會移

動一個內存中巨無霸,因爲它知道現在的CPU不便宜。通常,大對象具有很長的生存期,當一個大對象在.NET託管

堆中產生時,它被分配在堆的一個特殊部分中,移動大對象所帶來的開銷超過了整理這部分堆所能提高的性能。


Compact算法除了會提高再次分配內存的速度,如果新分配的對象在堆中位置很緊湊的話,高速緩存的性能將會

得到提高,因爲一起分配的對象經常被一起使用(程序的局部性原理),所以爲程序提供一段連續空白的內存空間是很

重要的。

 

垃圾收集器優點:

因爲我沒有很豐富的C/C++編程經驗,如果想談垃圾收集器的好處,那麼勢必要和C/C++這樣的較低級的語言對比。所以一般性的回答都是減少內存使用不當的BUG,提升編程效率之類的問題。

爲什麼要使用GC呢?也可以說是爲什麼要使用內存自動管理?有下面的幾個原因:

1、提高了軟件開發的抽象度; 
2、程序員可以將精力集中在實際的問題上而不用分心來管理內存的問題; 
3、可以使模塊的接口更加的清晰,減小模塊間的偶合; 
4、大大減少了內存人爲管理不當所帶來的Bug; 
5、使內存管理更加高效。 
總的說來就是GC可以使程序員可以從複雜的內存問題中擺脫出來,從而提高了軟件開發的速度、質量和安全性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章