1. 程式人生 > >CLR中的垃圾回收器

CLR中的垃圾回收器

託管堆:
在每個程式中都要使用資源,包括檔案、記憶體緩衝區、網路連線、資料庫資源等等。在面向物件中,每個型別都代表可供程式使用的一種資源。要使用資源,必須為代表資源的型別分配記憶體。那訪問一個資源需要哪些步驟呢?
1.呼叫IL指令newobj,為代表資源分配記憶體
2.初始化內容,設定資源的初始狀態並使資源可用。
3.訪問型別的成員來使用資源
4.摧毀資源的狀態以進行清理。
5.釋放記憶體。

原生的C++開發人員就需要手動管理記憶體,如果開發者忘記釋放不在需要的記憶體而造成記憶體洩漏,或者在試圖使用已經釋放的記憶體,然後由於記憶體被破壞而造成程式錯誤和安全漏洞。

C# 只需要寫的是可驗證的、型別安全的程式碼,應用程式就不可能會出現記憶體被破壞的情況。
託管堆的作用就是為開發人員提供一個簡化的程式設計模型:分配並初始化資源並直接使用。大多數型別都無需資源清理,垃圾回收器會自動釋放記憶體。

從託管堆分配資源:
在程序初始化時,CLR劃出一個地址空間區域作為託管堆。另外CLR還需要維護一個指標,該指標指向下一個物件在堆中的分配位置,預設為地址空間區域的基地址。
應用程式的內容受程序的虛擬地址空間限制,32位程序最多能分配1.5GB,64位程序最多能分配8TB。

分配流程:
1.C#使用new欄位,計算型別的欄位(包含從基類型別繼承的欄位)所需的位元組數。
2.加上物件的型別物件指標和同步塊索引。對32位應用程式,這個兩個欄位各自需要32位。對於64位應用程式,這個兩個欄位需要64位。
3.CLR檢查區域中是否有分配物件所需的位元組數。有足夠的可以用空間,就在維護的指標指向的地址放入物件。為物件分配的位元組會被清零。並呼叫型別的構造器,new操作符返回物件引用。在返回引用之前,會指標會加上物件佔用的位元組數來得到一個新值。即下個物件放入托管堆時的地址。

垃圾回收(GC)演算法:
CLR使用一種引用跟蹤演算法。該演算法只關心引用型別的變數,因為只有這種變數才能應用堆上的物件。值型別變數直接包含值型別例項。
引用型別的變數可在許多場合使用,包括類的靜態和例項欄位,或者方法的引數和區域性變數,我們將所有引用型別的變數都稱為根。

CLR執行GC流程:
1.暫停程序中所有執行緒,防止執行緒在CLR檢查期間訪問物件並更改狀態。
2.CLR進入GC標記階段:

1.遍歷堆中所有物件,將同步塊索引欄位中的一位設為0,表示所有物件都應刪除
2.CLR檢查所有活動根,檢視它們引用了哪些物件,如果一個根包含null,CLR忽略這個根並繼續檢查下個根。任何根引用了堆上的物件,CLR
都會標記那個物件,也就是將同步塊索引中的一位設為1.已標記物件是可達的,所以不能被垃圾回收,未標記物件是不可達的,因為應用程式中不存在使物件能被再次訪問的根。

3.標記完成後,進入GC壓縮階段:

1.CLR對堆中已標記的物件進行壓縮,他們佔用連續的記憶體空間。優點:搜點恢復了引用的“區域性化”,減少應用程式的工作集,提升了將來訪問這些物件時的效能,解決本機堆的空間碎片化問題。
2.CLR從每個根減去所引用的物件在記憶體中偏移的位元組數,重新制定根所引用的物件在堆中的位置。

4.設定指標指向最後一個物件之後的位置,然後恢復應用程式的所有執行緒,這些執行緒繼續訪問物件。

如果CLR在一次GC之後回收不了記憶體,而且程序中沒有空間來分配新的區域,說明該程序記憶體已耗盡。會丟擲OutOfMemoryException異常。

注意:
一但根離開作用域,它引用的物件就會變得“不可達”,GC會回收其記憶體

代:提升效能
CLR的GC是基於代的垃圾回收器,它對程式碼做出了幾點假設:
1.物件越新,生存期越短。
2.物件越老,生存期越長。
3.回收堆的一部分,速度快於回收整個堆。

代的工作原理:
第0代物件:託管堆的初始化時是不包含物件的。那些新構造的物件,垃圾回收器從未檢查過的物件,稱為第0代物件。

CLR初始化時為第0代物件選擇一個預算容量(kb為單位),如果分配一個新物件造成第0代物件超過預算,就必須啟動一次垃圾回收。

第一代物件:在第0代空間滿員後,再次新增的分配的物件,會啟動一次垃圾回收,未被銷燬物件在GC壓縮後成為第一代物件。

在經歷過垃圾回收後,第0代就不包含任何物件。和前面一樣,新物件會分配到第0代物件中。再次進行垃圾回收時,會檢查第1代佔用了多少記憶體,如果第一代佔用記憶體少於預算容量,所以垃圾回收只檢查第0代物件。

第二代物件:在第0代空間滿員後,會判斷第一代是否超出預算,如果超出 ,會檢查第1代和第0代物件,未被小隊的第1代物件提升至第2代物件,第0代物件提升至第1代物件。

託管堆只支援3代:第0代、第1代、第2代。
CLR初始化時,會為每一代選擇預算空間。CLR的垃圾回收器是自調節的。
如果GC,沒有回收到足夠記憶體,會執行一次完整回收,如果還是不夠,便會丟擲OutOfMemoryException異常。

垃圾回收的觸發條件:
CLR在檢測第0代超過預算時觸發一次GC,這是最常見的GC觸發條件。
其他條件:
1.顯示呼叫System.GC的靜態Collect方法
2.Windows報告低記憶體情況
3.CLR正在解除安裝AppDomain
4.CLR正在關閉

大小物件:
CLR將物件分為大物件和小物件,目前認為85000位元組以上的物件是大物件。CLR以不同的方式對待大小物件:
1.大物件不是在小物件的地址空間分配,而是在程序地址空間的其他地方分配。
2.目前版本GC不壓縮大物件。在程序中的大物件之間造成地址空間的碎片化,以至於丟擲OutOfMemoryException。
3.大物件總是第二代,絕不可能是第0代或是第二代。

垃圾回收模式:
CLR啟動時會選擇一個GC模式,程序終止前該模式不會改變。
1.工作站

該模式針對客戶端應用程式優化GC。GC造成的延時低,應用程式執行緒掛起時間短。該模式中,GC假定機器上執行的其他應用程式都不會消耗太多的CPU資源

2.伺服器

該模式針對伺服器端應用程式優化GC。被優化的主要是吞吐量和資源利用。GC假定機器上沒有執行其他應用程式,假定機器的所有CPU都可用來輔助完成GC。該模式造成託管堆被拆分成幾個區域,每個CPU一個。開始垃圾回收時,來及回收器在每個CPU上都執行一個特殊執行緒;每個執行緒都和其他執行緒併發回收它自己的區域。對於工作者執行緒行為一致的伺服器應用程式,併發能很好的進行

兩種子模式:
1.併發
2.非併發

強制垃圾回收:
System.GC型別允許應用程式對垃圾回收器進行一些直接控制。
抵用GC類的Collect 方法可以強制垃圾回收。

需要特殊清理的型別
包含本機資源的型別被GC時,GC會回收物件在託管堆中使用的記憶體,但會造成本機資源的洩漏,所以CLR提供了終結的機制,在物件被判定為垃圾後,在物件回收之前執行一些程式碼。任何包裝了本機資源的型別都支援終結。
System.Object 定義了受保護的虛方法Finalize。垃圾回收器判定物件是垃圾後,會呼叫物件的Finalize方法。