標籤:style c class blog code java
GC(Garbage Collector,記憶體回收行程)是一種自動回收記憶體的機制,釋放已經不再使用的對象的記憶體空間。
在.NET平台中,我們的Managed 程式碼一般都不再關心記憶體的管理,一切都有CLR(Common language Runtime)去幫我們完成了。當我們開闢記憶體空間用來建立對象時,使用new關鍵字,這時CLR會分配一塊記憶體存放對象,大部分時候,我們都不用自己去釋放記憶體空間,而是由CLR在某個適當的時候幫我們釋放掉。
為什麼要GC?
1.建立新對象開闢記憶體空間,在使用完後需要釋放記憶體,提高效能
2.避免開發人員直接操作記憶體,提高安全性
GC的過程
我們運行.NET程式後,OS Loader首先識別出IL,然後會載入CLR的核心庫,進行一系列的必要處理後,CLR來到我們編寫的代碼入口處執行。
當我們的在代碼中使用new操作符建立class時,CLR便在叫作GC堆(GC Heap)的記憶體地區上分配一塊記憶體存放我們的對象,若對象的Size超過85K位元組時,考慮到效能原因,將對象建立在LOH(Large Object Heap)上而不是GC堆上【注1】,若我們在class中定義了解構函式來釋放非託管資源【注2】,則CLR會在一個叫做終結器隊列(Finalizer Queue)的地方添加一個指向該class的項。
我們的程式在啟動並執行過程,在某個時候需要進行記憶體回收了【注3】,首先GC會暫時掛起所有線程,然後確定對象引用的roots【注4】,並根據參考關聯性建立出由roots出發可以達到的對象形成的對象圖,這些對象暫時還在使用,而那些已建立的卻不在對象圖中的對象則是不可達到的,也就是垃圾了,屬於要回收的對象。隨後將仍然使用的對象移動到存活期更久的地區【注5】,更改地區指標以回收對象,壓縮記憶體去除記憶體空隙,並修複對移動的仍存活對象的引用指標,對於有解構函式的對象,則第一次回收時不會回收,而是將其在終結器隊列中移除,並添加到另一個標為準備終止的對象列表中,另一個GC線程會調用此列表指向的對象的Finalize(),回收非託管資源,然後將項從列表中移除,下一次的GC才會真正回收掉該對象。
注1:對象建立在Heap上的細節
1)為了更高效的進行GC,.NET將GC堆分成了3個代,Gen0,Gen1和Gen2。
2)這3個代只是邏輯上的劃分,在記憶體中,他們的地址是連續的。
3)Gen0和Gen1之和的大小大約是16M(workstation GC模式下)和64M(server GC模式下)。
4)新建立對象Size小於85k位於Gen0上,大於85K的則建立在LOH上。
注2:定義解構函式釋放非託管資源
Finalize方法是用來釋放對象中使用的非託管資源,他是作為Dispose()方法的一種安全防護措施,即代碼中沒有顯示的調用Dispose()來釋放非託管資源時,GC時調用Finalize方法來釋放,Finalize方法中並不直接釋放非託管資源,而是調用Dispose(false)來釋放。自.NET2.0起,C#中不能直接override Finalize方法,是通過解構函式來實現,解構函式在IL中會被解釋為:
protected override void Finalize() { try { //執行自訂資源清理操作 } finally { base.Finalize(); } }
預設情況下,一個類是沒有解構函式的,那麼在GC時是不會調用其Finalize()方法的。
注3:GC發生的時機
1)當Gen0的記憶體使用量達到一個閾值時,將引發Gen0的GC,同理Gen1達到時,會Gen0和Gen1同時GC,若Gen2達到時,則會引發Full GC
2)Windows報告記憶體不足時
3)調用GC.Collect時
4)其他情況:CLR卸載AppDimain,實體記憶體不足等
注4:確定對象引用根
對象的引用根主要來自於:FInalize Queue,CPU寄存器中的對象指標,全域對象、靜態變數、局部對象、函數調用參數等。
注5: GC時對象的轉移
1)Gen0 GC時,會將Gen0中存活的對象整體移動到Gen1中,然後壓縮Gen1,使Gen1中的記憶體連續,同理Gen1中移動到Gen2。
2)Gen2 GC時,此時發生的GC也稱為Full GC,會回收整個Heap上的對象,Gen2上的對象將不再移動,而是壓縮記憶體空間。
3)LOH中的對象在Full GC時被回收,但其記憶體不會被壓縮,而是使用一個空閑列表free list記錄LOH中的空閑空間,對釋放出來的空間進行管理。
4)若對象是pinned object,則此對象不能被移動, 會造成記憶體片段。