.NET垃圾收集關鍵方法解析
之前51cto曾報道過關于.NET垃圾收集機制中的算法與代齡,說到.NET垃圾收集,就不得不提到其中的關鍵方法,其是實現.NET GC運行機制的前提。
1.Dispose()方法
Dispose可用于釋放所有資源,包括托管的和非托管的,需要自己實現。大多數的非托管資源都要求手動釋放,我們應當為釋放非托管資源公開一個方法,實現釋放非托管資源的方法有很多種,實現IDispose接口的Dispose方法是***的,這可以給使用你類庫的程序員以明確的說明,讓他們知道怎樣釋放你的資源;而且C#中用到的using語句快,也是在離開語句塊時自動調用Dispose方法。
這里需要注意的是,如果基類實現了IDispose接口,那么它的派生類也必須實現自己的IDispose,并在其Dispose方法中調用基類中Dispose方法。只有這樣的才能保證當你使用派生類實例后,釋放資源時,連同基類中的非托管資源一起釋放掉。
使用using與try+finally的區別
可以說二者沒有任何區別,因為using只是編輯器級的優化,它與try+finally有著相同的作用,以下是一段使用using的代碼,它在IL階段也是以try+finally呈現的,但是,using的優點是,在代碼離開using塊時,using會自動調用Idispose接口的Dispose()方法。
- public partial class _Default : System.Web.UI.Page
- {
- protected void Page_Load(object sender, EventArgs e)
- {
- using (DataSet ds = new DataSet())
- {
- }
- }
- }
- .method family hidebysig instance void Page_Load(object sender,class [mscorlib]System.EventArgs e) cil managed
- {
- // 代碼大小 29 (0x1d)
- .maxstack 2
- .locals init ([0] class [System.Data]System.Data.DataSet ds,
- [1] bool CS$4$0000)
- IL_0000: nop
- IL_0001: newobj instance void [System.Data]System.Data.DataSet::.ctor()
- IL_0006: stloc.0
- .try
- {
- IL_0007: nop
- IL_0008: nop
- IL_0009: leave.s IL_001b
- } // end .try
- finally
- {
- IL_000b: ldloc.0
- IL_000c: ldnull
- IL_000d: ceq
- IL_000f: stloc.1
- IL_0010: ldloc.1
- IL_0011: brtrue.s IL_001a
- IL_0013: ldloc.0
- IL_0014: callvirt instance void [mscorlib]System.IDisposable::Dispose()
- IL_0019: nop
- IL_001a: endfinally
- } // end handler
- IL_001b: nop
- IL_001c: ret
- } // end of method _Default::Page_Load
2. GC.Collect()方法
如果我們在程序中顯式的調用了垃圾收集器的collect接口,那么垃圾收集器會立即運行,完成內存對象的標記、壓縮與清除工作,使用GC.Collect(i)還可以指定回收的代,然而aicken并不贊成各位同學顯式調用它:
(1)GC.Collect()做的并不只是回收內存,就像***節中介紹的,在回收了內存之后,GC會重新整理內存,修正對象指針,讓空閑內存連續,供CLR順序分配內存,提高新建對象的效率。內存壓縮整理工作非常耗用計算資源。
(2)很少有人會關心到GC除了在內存吃緊以及資源空閑時運行,還會在什么時候運行。 其實GC的運行時機,還要受到一個叫做“策略引擎”的部件控制,它會觀察GC的收集頻率、效率等等。它會根據GC回收效果,調整GC運行的頻率:即當某次GC回收效果頗豐時,它便會增加GC運行的頻率,反之亦然。
所以如果剛剛發生了一次自然的收集,垃圾對象就會非常之少,而此時程序又顯式的進行了收集調用,那么自然, GC雖然小有收獲,但是策略引擎就會認為:這很不值得,才收集了這么點垃圾,也許該減少GC的次數。這樣一來,垃圾收集器努力保持的自然節奏就被打亂了,同時,對象類型的創建效率與頻率,也會被“策略引擎”捕捉到,從而改變代的數量與容量。
所以,額外的調用GC,代價高昂,甚至會降低效率。顯示的調用GC.Collect(),實質是在用“時間換空間”,而通常在程序設計中,我們推薦的設計原則是“空間換時間”,比如使用各種各樣的緩存,也有例外,如果你掌握了整個應用程序的情況,明確的知道何時會產生大量垃圾,也是可以顯示調用該方法的。綜上,盡量不要顯示調用GC.Collect(),因為服務器的CPU比內存要貴的多! #p#
3. 析構函數(Finalize())
我們知道,GC只負責釋放托管資源,非托管資源GC是無法釋放的。類似文件操作、數據庫連接等都會產用非托管資源。Finalize方法是用于釋放非托管資源的,等同于C#中是析構函數,C#編譯器在編譯構造函數時,會隱式的將析構函數編譯為Finalize()對應的代碼,并確定在finally塊中執行了base.Finalize()。析構函數中只能釋放非托管資源,而不要在任何托管資源進行析構,原因如下:
(1)你無法預測析構函數的運行時機,它不是按順序執行的。當析構函數被執行的時候,也許你進行操作的托管資源已經被釋放了。
(2)包含Finalize()的對象,需要GC的兩次處理才能刪除。
(3)CLR會在單獨的線程上執行所有對象的Finalize()方法,無疑,如果頻繁的Finalize(),會降低系統的性能。
下面我們來重點說說第(2)點,為何包含Finalize()的對象,需要兩次GC才能被清除。首先要了解與Finalize相關的兩個隊列:終止隊列(Finalization Queue)與可達隊列(Freachable Queue),這兩個隊列存儲了一組指向對象的指針,當程序中在托管堆上分配空間時(new),如果該類含有析構函數,GC將在Finalization Queue中添加一個指向該對象的指針。
在GC***運行時,會在已經被確認為垃圾的對象中遍歷,如果某個垃圾對象的指針被Finalization Queue包含,GC將這個對象從垃圾中分離出來,將它的指針儲存到Freachable Queue中,并在Finalization Queue刪除這個對象的指針記錄,這時該對象就不是垃圾了——這個過程被稱為是對象的復生(Resurrection)。當Freachable Queue一旦被添加了指針之后,它就會去執行對象的Finalize()方法,清除對象占用的資源。
當GC再次運行時,便會再次發現這個含有Finalize()方法的垃圾對象,但此時它在Finalization Queue中已經沒有記錄了(GC***運行時刪掉了它的Finalization Queue記錄),那么這個對象就會被回收了,至此,通過GC兩次運行,終于回收了帶有析構函數的對象。復活實例:
- private void Form1_Load(object sender, EventArgs e)
- {
- Resource re = new Resource();
- re = null;GC.Collect();
- GC.WaitForPendingFinalizers();
- //***GC.Collect()沒起作用哦。
- label1.Text = re.num.ToString();
- }
- public class Resource
- {
- public int num;
- ~Resource()
- {
- }
- }
看了上面的代碼,大家應該了解什么是復活了吧!那么為什么要復生呢?因為***GC時,這個對象的Finalize()方法還沒有被執行,如果不經過復生就被GC掉,那么就連它的Finalize()一起回收了,Finalize()就無法運行了,所以必須先復生,以執行它的Finalize(),然后再回收。
還有兩個方法ReRegisterForFinalize和SuppressFinalize需要講一講,ReRegisterForFinalize是將指向對象的指針重新添加到Finalization Queue中(即召喚系統執行Finalize()方法),SuppressFinalize是將對象的指針從Finalization Queue中移除(即拒絕系統執行Finalize()方法)。
SuppressFinalize用于那些即有析構函數來釋放資源,又實現了Dispose()方法釋放資源的情況下:將GC.SuppressFinalize(this)添加至Dispose()方法中,以確保程序員調用Dispose()后,GC就不必再次收集了,即實現Idisposable中的Dispose()方法,又使用析構函數,一個雙保險,大家不要迷惑,其實在釋放非托管資源時,使用一個即可,推薦使用前者。代碼如下:
- public class Resource : Idisposable
- {
- private bool isDispose = false;
- //實現Dispose(),后面還有析構函數,以防程序員忘記調用Dispose()方法
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- protected virtual void Dispose(bool disposing)
- {
- if (!isDispose)
- {
- if (disposing)
- {
- //清理托管資源
- }
- //清理非管資源
- }
- isDispose = true;
- }
- Resource ()
- {
- Dispose(false);
- }
- }
4.弱引用(WeakReference)
***一個話題:弱引用。在編程中,對于那些大對象建議使用這種引用方式,這種引用不影響GC回收:我們用過了某個對象,然后將其至null,這樣GC就可以快速回收它了,但是沒過多久我們又需要這個對象了,沒辦法,只好重新創建實例,這樣就浪費了創建實例所需的計算資源;而如果不至null,就會浪費內存資源。對于這種情況,我們可以創建一個這個大對象的弱引用,這樣在內存不夠時GC可以快速回收,而在沒有被GC回收前我們還可以再次利用該對象。
- public class SomeObject
- {
- }
- public static void Main()
- {
- SomeObject so = new SomeObject();
- WeakReference WRso = new WeakReference(so);
- so = null;
- Console.WriteLine(WRso.IsAlive); // True
- // 調用GC 手動回收。
- GC.Collect();
- Console.WriteLine(WRso.IsAlive); // False
- }
看到沒,在so = null;后,它的弱引用依然是可用的。所以對于大對象的使用,aicken建議使用此種方式。另外,弱引用有長短之分:長弱引用在對象終結后,依然追蹤對象;短弱引用則反之,aicken不建議人為干預GC的工作成果,所以推薦使用短弱引用,即上面代碼中的方式。
文章原標題:.NET Discovery系列之三--深入理解.NET垃圾收集機制(下)
原文鏈接:http://www.cnblogs.com/isline/archive/2009/03/04/1402713.html
【編輯推薦】