什么是GC
Garbage Collector(垃圾收集器)以應用程序的root為基礎,遍歷應用程序在Heap上動態分配的所有對象,通過識別它們是否被引用來確定哪些對象是已經死亡的哪些仍需要被使用。已經不再被應用程序的root或者別的對象所引用的對象就是已經死亡的對象,即所謂的垃圾,需要被回收。這就是GC工作的原理。
為了實現這個原理,GC有多種算法。比較常見的算法有Reference Counting,Mark Sweep,Copy Collection等等。目前主流的虛擬系統.net CLR,Java VM和Rotor都是采用的Mark Sweep算法。
托管堆中存放引用類型對象,因此GC的內存管理的目標主要都是引用類型對象。
GC與內存管理
一個對象的生命周期簡單概括就是:創建>使用>釋放,在.NET中一個對象的生命周期:
- new創建對象並分配內存
- 對象初始化
- 對象操作、使用
- 資源清理(非托管資源)
- GC垃圾回收
創建一個新對象的主要流程:
GC垃圾回收
- GC 0/1/2代:代齡(Generation);
- 大對象堆(Large Object Heap),大於85000字節的大對象會分配到這個區域,這個區域的主要特點就是:不會輕易被回收;就是回收了也不會被壓縮(因為對象太大,移動復制的成本太高了);
- 垃圾:沒有被引用的對象
垃圾回收的基本流程
1.標記:根據應用程序根指針Root遍歷堆上的每一個引用對象,對於還在使用的對象(可達對象)進行標記
2.清除:針對所有無引用對象進行清除,針對普通對象直接回收內存,而對於實現了終結器的對象(實現了析構函數的對象)需要單獨回收處理。清除之后,內存就會變得不連續了,就是步驟3的工作了。
3.壓縮:把剩下的對象轉移到一個連續的內存,因為這些對象地址變了,還需要把那些Root跟指針的地址修改為移動后的新地址。
關於代齡(Generation)
- 第0代,最新分配在堆上的對象,從來沒有被垃圾收集過。任何一個新對象,當它第一次被分配在托管堆上時,就是第0代(大於85000的大對象除外)。
- 第1代,0代滿了會觸發0代的垃圾回收,0代垃圾回收后,剩下的對象會搬到1代。
- 第2代,當0代、1代滿了,會觸發0代、1代的垃圾回收,第0代升為第1代,第1代升為第2代。
非托管資源回收
.NET中提供釋放非托管資源的方式主要是:Finalize() 和 Dispose()。
Dispose():
常用的大多是Dispose模式,實現IDisposable接口,Dispose需要手動調用,在.NET中有兩中調用方式:
//方式1:顯示接口調用 SomeType st1=new SomeType(); //do sth st1.Dispose(); //方式2:using()語法調用,自動執行Dispose接口 using (var st2 = new SomeType()) { //do sth }
using只是一種語法形式,本質上還是try…finally的結構
Finalize() :終結器(析構函數)
它作用就是用來釋放非托管資源,由GC來執行回收,因此可以保證非托管資源可以被釋放。
它是來自System.Object中受保護的虛方法Finalize,無法被子類顯示重寫,也無法顯示調用。
所有實現了終結器(析構函數)的對象,會被GC特殊照顧,GC的終止化隊列跟蹤所有實現了Finalize方法(析構函數)的對象。
- 當CLR在托管堆上分配對象時,GC檢查該對象是否實現了自定義的Finalize方法(析構函數)。如果是,對象會被標記為可終結的,同時這個對象的指針被保存在名為終結隊列的內部隊列中。終結隊列是一個由垃圾回收器維護的表,它指向每一個在從堆上刪除之前必須被終結的對象。
- 當GC執行並且檢測到一個不被使用的對象時,需要進一步檢查“終結隊列”來查詢該對象類型是否含有Finalize方法,如果沒有則將該對象視為垃圾,如果存在則將該對象的引用移動到另外一張Freachable列表,此時對象會被復活一次。
- CLR將有一個單獨的高優先級線程負責處理Freachable列表,就是依次調用其中每個對象的Finalize方法,然后刪除引用,這時對象實例就被視為不再被使用,對象再次變成垃圾。
- 下一個GC執行時,將釋放已經被調用Finalize方法的那些對象實例。
GC在哪些情況下回進行回收工作?
- 內存不足溢出時(0代對象充滿時)
- Windwos報告內存不足時,CLR會強制執行垃圾回收
- CLR卸載AppDomian,GC回收所有
- 調用GC.Collect
- 其他情況,如主機拒絕分配內存,物理內存不足,超出短期存活代的存段門限