CLR via C#:託管堆和垃圾回收

託管堆:指的是進程初始化時,CLR開闢的一個地址空間區域。具有以下特性:
1.存儲大對象的託管堆稱爲大對象堆;存放小對象的託管堆稱爲小對象堆。
2.託管堆內部維護一個NextObjPtr指針來指向下一個對象的分配位置。
3.靜態字段引用的對象一直存在,直到用於加載類型的AppDomain卸載爲止。

:指的是引用類型的變量。具有以下特性:
1.當C#編譯器使用/debug開關時,根的生命週期延長至函數的生命週期結束。
2.當C#編譯器不使用/debug開關時,根的生命週期不保證在函數的生命週期中自始至終的存活。

:指的是託管堆中的區域。具有以下特性:
1.CLR現在只支持三代,分別爲第0代,第1代以及第2代。
2.CLR初始化以及垃圾回收後都會動態調整三代各自的預算。
3.代對代碼做出以下假設:
1>.對象越新,生存期越短。
2>.對象越舊,生存期越長。
3>.回收託管堆的一部分,速度快於回收整個託管堆。

大對象:指的是佔用內存大於或等於85000字節的對象。具有以下特性:
1.大對象存放在大對象堆的第2代中。
2.垃圾回收器不對存活的大對象進行移動,從而造成大對象堆的第2代地址空間碎片化。

訪問資源:流程如下所示:
1.調用IL的newobj指令,爲代表資源的類型分配內存。
2.初始化內存,設置資源初始化狀態並使資源可用。
3.訪問類型的成員來使用資源。
4.由垃圾回收器自動釋放內存。

分配資源:CLR使用new操作符來分配資源。流程如下所示:
1.計算類型的字段(包含從基類繼承的字段)所需的字節數。
2.計算對象的開銷(包含類型對象指針和同步塊索引)所需的字節數。
3.當對象是大對象時,執行流程如下所示:
1>.CLR檢查大對象堆的第2代沒有超過預算時,就會在大對象堆的NextObjPtr指針指向的地址處放入大對象,並將大對象分配的字節清零;然後調用類型的實例構造函數(爲this參數傳遞NextObjPtr),並在new操作符返回大對象引用之前將大對象堆的NextObjPtr指針的值加上大對象佔用的字節數,從而得到下一個大對象放入大對象堆的第2代時的地址。
2>.CLR檢查大對象堆的第2代超過預算時,就會執行垃圾回收。如果沒有回收足夠內存時,就會拋出OutOfMemoryException;否則就執行步驟1>。
4.當對象是小對象時,執行流程如下所示:
1>.CLR檢查小對象堆的第0代沒有超過預算時,就會在小對象堆的NextObjPtr指針指向的地址處放入小對象,並將小對象分配的字節清零;然後調用類型的實例構造函數(爲this參數傳遞NextObjPtr),並在new操作符返回小對象引用之前將小對象堆的NextObjPtr指針的值加上小對象佔用的字節數,從而得到下一個小對象放入小對象堆的第0代時的地址。
2>.CLR檢查小對象堆的第0代超過預算時,就會執行垃圾回收。如果沒有回收足夠內存時,就會拋出OutOfMemoryException;否則就執行步驟1>。

垃圾回收觸發條件:如下所示:
1.開發人員在代碼中主動調用GC的Collect函數時,就會執行一次垃圾回收。
2.CLR在分配資源的過程中檢查代佔用內存超過預算時,就會執行一次垃圾回收。
3.CLR內部使用CreateMemoryResourceNotification和QueryMemoryResourceNotification函數監視系統報告低內存時,就會執行一次垃圾回收。
4.CLR正在卸載AppDomain時,就會執行一次垃圾回收。
5.CLR正在關閉時,所有對象有機會進行資源清理,系統也將回收進程的全部內存。

垃圾回收模式:具有以下特性:
1.工作站主模式:該模式針對客戶端應用程序優化垃圾回收。
2.服務器主模式:該模式針對服務端應用程序優化垃圾回收。
3.併發子模式:垃圾回收器有一個額外的後臺線程,它能在應用程序運行時併發標記對象。
4.非併發子模式。
5.CLR啓動時會選擇一種主模式和子模式。可以在應用程序配置文件中通過gcServer元素的enabled屬性來設置是否使用服務器主模式;通過gcConcurrent元素的enabled屬性來設置是否使用併發子模式。
6.可以使用GCSettings類的IsServerGC屬性來判定是否處於服務器主模式。
7.可以使用GCSettings類的LatencyMode屬性來對垃圾回收進行某種程度的控制。
常見控制如下表所示:

符號名稱 說明
Batch(服務器主模式的默認值) 關閉併發子模式
Interactive(工作站主模式的默認值) 打開併發子模式
LowLatency 在短期的,時間敏感的操作中使用這個延遲模式。這些操作不適合對第2代進行回收
SustainedLowLatency 使用這個延遲模式,應用程序的大多數操作都不會發生長的垃圾回收暫停。只要有足夠的內存,它將禁止所有會造成阻塞的第2代回收動作。

垃圾回收執行過程:CLR採用引用跟蹤算法,該算法只關心根。執行流程如下所示:
1.CLR暫停進程中的所有線程,從而防止線程訪問對象並更改其狀態。
2.CLR遍歷小對象堆的三代以及大對象堆的第2代,並將"代的佔用內存"超過"代的預算"的代中所有對象添加到"代對象列表"中。
3.CLR進入標記階段。該階段執行流程如下所示:
1>.CLR將代對象列表中所有對象標記成可回收(也就是將對象的同步塊索引字段中的一位設置成0)。
2>.CLR查找所有活動根,並執行以下流程:
1>>.根引用null時,CLR就會忽略這個根並繼續檢查下一個根。
2>>.根引用對象時,當CLR檢查該對象不在代對象列表中或者該對象已經被標記成不可回收時,就會忽略這個根並繼續檢查下一個根;否則就會將該對象標記成不可回收(也就是將對象的同步塊索引字段中的一位設置成1),然後將該對象中的所有根都執行1>>和2>>步驟。
4.CLR進入壓縮階段,並執行以下流程:
1>.將代對象列表中不可回收的小對象進行移動(也就是先將第1代小對象移動到第2代,然後將第0代的小對象移動到第1代),使它們佔用連續的內存空間。
2>.CLR將根減去引用小對象在內存中偏移的字節數,從而使根還是引用之前一樣的小對象。
5.CLR將小對象堆的NextObjPtr指針指向最後一個不可回收對象之後的位置,該位置就是小對象堆的第0代起始位置。
6.CLR將大對象堆的NextObjPtr指針指向某一個回收大對象的位置,該位置就是大對象堆的第2代起始位置。
7.CLR恢復進程中的所有線程,這些線程可以繼續訪問對象,就像沒有進行垃圾回收一樣。

強制垃圾回收:就是使用GC類型對應用程序進行一些控制。具有以下特性:
1.GCCollectionMode枚舉定義如下表所示:

符號名稱 說明
Default 等同於不傳遞任何符號名稱。目前還等同於傳遞Forced,但CLR未來版本可能對此進行修改
Forced 強制回收指定的代(以及低於它的所有代)
Optimized 只有在能釋放大量內存或者能減少碎片化的前提下,才執行回收。如果垃圾回收沒有任何效益,當前調用就沒有任何效果

2.Collect函數用來強制垃圾回收。一般情況下不建議調用該函數,而是讓垃圾回收器自行斟酌執行。但是以下情形可以考慮手動調用該函數:
1>.發生了某個非重複性事件,並導致大量舊對象死亡,就可以手動調用一次Collect函數。
3.對於一次垃圾回收需要花很長時間才能完成時,可以使用RegisterForFullGCNotification,WaitForFullGCApproach,WaitForFullGCComplete以及CancelForFullGCNotification等函數來使應用程序在垃圾回收器將要執行完全回收時收到通知,從而做出相應的邏輯處理。
4.CollectionCount函數用來查看某一代發生了多少次垃圾回收。
5.GetTotalMemory函數用來查看託管堆中對象當前使用了多少內存。
6.AddMemoryPressure函數用來監視內存壓力變大時,就強制執行垃圾回收。
7.RemoveMemoryPressure函數用來取消監視內存壓力。
8.HandleCollector類型對象會在內部監視資源的計數,當計數超過閾值時就強制垃圾回收。

終結機制:一種用來釋放本機資源而設計的機制。具有以下特性:
1.C#要求在類名前添加~符號來定義Finalize函數。
2.終結機制內部工作原理如下所示:
1>.在調用對象的構造函數前,如果該對象重寫了Object的Finalize函數的話,CLR就會將指向該對象的引用存放在終結列表中。
2>.一次垃圾回收時,垃圾回收器在託管堆上的標記可回收對象時,如果該對象在在終結列表中存在引用的話,CLR就會從終結列表中移除該對象的引用;然後將該對象的引用存放在freachable隊列中;最後將託管堆上該對象標記成不可回收。
3>.CLR使用特殊的終結線程來檢查freachable隊列是否爲空。當freachable隊列爲空時,該線程就會休眠;否則該線程就會被喚起,然後清空freachable隊列並執行每個對象的Finalize函數。
4>.下一次進行垃圾回收時,已終結的對象要被垃圾回收器真正的回收掉,必須滿足以下條件:
1>>.垃圾回收器要回收的代中包含已終結的對象。
2>>.沒有根關聯到已終結對象。
3.Finalize函數問題較多,使用須謹慎。常見的問題如下所示:
1>.Finalize函數的執行時間是沒法手動控制的。
2>.Finalize函數的執行順序是沒法手動控制的。
3>.調用了Finalize函數的對象會被提升到下一代,直到下一次進行垃圾回收時纔有可能被回收掉,從而增大內存耗用。
4>.如果某一個Finalize函數發生阻塞,終結線程就調用不了其他的Finalize函數,從而造成本機資源內存泄漏。
4.強烈建議不要重寫Object的Finalize函數,而是使用FCL提供的輔助類來完成終結操作。常見的輔助類如下所示:
1>.CriticalFinalizerObject抽象類具有以下特性:
1>>.首次構造CriticalFinalizerObject派生類型的對象時,CLR立即對繼承層次結構中的所有
Finalize函數進行JIT編譯。
2>>.CLR是在調用了非CriticalFinalizerObject派生類型的Finalize函數之後,才調用CriticalFinalizerObject派生類型的Finalize函數。
3>>.如果AppDomain被一個宿主應用程序強行中斷,CLR將調用CriticalFinalizerObject派生類型的Finalize函數,從而確保本機資源得以釋放。
2>.SafeHandle抽象類具有以下特性:
1>>.具有CriticalFinalizerObject抽象類的所有特性。
2>>.SafeHandle派生類型必須重寫受保護的構造函數,釋放資源的抽象函數ReleaseHandle以及是否句柄無效的抽象屬性IsInvalid。
3>>.與本機代碼互操作時,SafeHandle派生類型將獲得CLR的特殊支持。如:自動在託管堆上構建SafeHandle派生類型實例。
4>>.SafeHandle派生類型內部通過引用計數來防止有人利用潛在的安全漏洞。其中DangerousAddRef函數用來增加引用計數;DangerousRelease函數用來減少引用計數;DangerousGetHandle函數用來獲取原始句柄。
3>.CriticalHandle抽象類具有以下特性:
1>>.不提供引用計數功能,其他方面與SafeHandle抽象類型相同。
2>>.由於沒有引用計數,所以CriticalHandle的性能要高於SafeHandle;但是CriticalHandle的安全性要低於SafeHandle。
5.dispose模式:實現了IDisposable接口,就實現了dispose模式。具有以下特性:
1>.類型中的Dispose函數,在調用了一次之後再次調用時就直接返回。
2>.類型中的非Dispose函數和屬性,在調用了一次Dispose函數之後再次調用時就拋出一個ObjectDisposedException。
3>.Dispose函數不會將對象從託管堆上回收,只是標記對象被清理而已。
4>.對支持終結機制的類型而言(如FileStream),儘量不要在代碼裏顯示調用Dispose函數,而是應該交給垃圾回收器去回收對象。
5>.對不支持終結機制的類型而言(如StreamWriter),就要在代碼裏顯示調用Dispose函數來清理對象。

GC句柄表:允許應用程序監視或者手動控制對象的生存期。具有以下特性:
1.GCHandleType用來指定監視或者控制對象的標誌。其定義如下表所示:

標誌 描述
Weak 可監視垃圾回收器在什麼時候判定對象在應用程序中不可達(也就是標記爲可回收),此時對象的Finalize函數可能已經執行,對象可能還在內存中。
WeakTrackResurrection 可監視垃圾回收器在什麼時候判定對象在應用程序中不可達(也就是標記爲可回收),此時對象的Finalize函數已經執行,對象的內存已經被回收。
Normal 可控制垃圾回收器在應用程序中沒有根引用對象時,該對象也必須留在內存中。當垃圾回收發生時,該對象的內存可以被壓縮(移動)。
Pinned 可控制垃圾回收器在應用程序中沒有根引用對象時,該對象也必須留在內存中。當垃圾回收發生時,該對象的內存不能被壓縮(移動)。

2.垃圾回收器和GC句柄表的交互行爲如下所示:
1>.垃圾回收器掃描GC句柄表;然後將所有Normal或Pinned對象都標記成不可回收。
2>.垃圾回收器掃描GC句柄表;如果一個Weak記錄項引用了可回收對象,那麼該記錄項引用值更改爲null。
3>.垃圾回收器掃描GC句柄表;如果一個WeakTrackResurrection記錄項引用了可回收對象,那麼該記錄項引用值更改爲null。
4>.垃圾回收器進行內存壓縮(移動)時,Pinned對象不會被移動。
3.C#的fixed語句可以在一個代碼塊中固定對象。
4.WeakReference類型用來保存指定對象的弱引用,並且控制標誌爲Weak。
5.ConditionalWeakTable類用來將對象和數據進行關聯,其中對象保持弱引用,並且控制標誌爲Weak。當對象不被回收時,數據就一定存在。

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