.Net垃圾回收機制

.net中的資源有兩種,託管資源和非託管資源。下面來嘗試分析下這兩種類型資源的回收。

1.非託管資源回收

兩種方式:Dispose,Finalize

1.1Dispose

Dispose方法,繼承IDisposable接口,也就會自動調用Dispose方法。就可以在using裏創建對象了。

 class Student : IDisposable
    {
        public void Dispose()
        {
            throw new NotImplementedException();
        }
    }

調用的時候:

 Student stu = new Student();
            stu.Dispose();

或者:

using (Student stu1 = new Student())
            { 
                
            }

using的好處就是,在大括號範圍之外,對象就自動釋放,無需顯式調用dispose方法。

1.2Finalize()方法(不推薦)

class Student : IDisposable
    {
        ~Student() { }
    }

~Student()是析構函數,會隱式調用Finalize方法,即clr將析構函數隱式轉換爲:

protected override void Finalize()  
{  
    try  
    {  
       // Cleaning up .  
    }  
    finally  
    {  
       base.Finalize();  
    }
}

引用一下網上有大神的博客:

在.NET中應該儘可能的少用析構函數釋放資源,MSDN2上有這樣一段話:
  實現 Finalize 方法或析構函數對性能可能會有負面影響,因此應避免不必要地使用它們。用 Finalize 方法回收對象使用的內存需要至少兩次垃圾回收。當垃圾回收器執行回收時,它只回收沒有終結器的不可訪問對象的內存。這時,它不能回收具有終結器的不可訪問對象。它改爲將這些對象的項從終止隊列中移除並將它們放置在標爲準備終止的對象列表中。該列表中的項指向託管堆中準備被調用其終止代碼的對象。垃圾回收器爲此列表中的對象調用 Finalize 方法,然後,將這些項從列表中移除。後來的垃圾回收將確定終止的對象確實是垃圾,因爲標爲準備終止對象的列表中的項不再指向它們。在後來的垃圾回收中,實際上回收了對象的內存。

  所以有析構函數的對象,需要兩次,第一次調用析構函數,第二次刪除對象。而且在析構函數中包含大量的釋放資源代碼,會降低垃圾回收器的工作效率,影響性能。所以對於包含非託管資源的對象,最好及時的調用Dispose()方法來回收資源,而不是依賴垃圾回收器。

2.託管資源由GC回收

先弄清楚一個概念:

CLR使用了“代”概念來優化垃圾回收器,代是垃圾回收機制使用的一個邏輯技術,也是一種算法,它把託管堆中的內存分爲3個代

(截止到目前.NET Framework4.0有3個代:0、1、2)。

在激活一個進程時,CLR會先保留一塊連續的內存,在主線程啓動過程中,可能會初始化一系列對象,CLR先計算對象大小及其開銷所佔用的字節數,接着會在連續的內存塊中爲這些對象分配內存,這些對象被配置在第0代內存,在構造第0代內存的時候會分配一個默認大小的內存,隨着程序的運行,可能會初始化更多的對象,CLR發現第0代內存不能裝載更多的新生對象,此時CLR會啓動垃圾回收器對第0代內存進行回收,不再使用的對象所佔用的內存會被釋放,接着把0代對象提升爲第1代,然後把新生對象配置在第0代內存區中。CLR使用了3個階段的代,每次新分配的對象都會被配置在第0代內存中,最老的對象在第2代內存中,每次爲新對象分配內存時,都可能會進行垃圾回收以釋放內存。

垃圾回收過程:

1.查找根

clr驗證線程棧上行查找根(靜態字段、方法參數、局部變量、寄存器中的對象等等)。

2.標記根

標記查找到的所有跟,如果跟引用了對象A,則將A標記,如果對象A引用了對象B,則將B標記。如果多個對象都引用了一個對象C,clr會判斷已經被標記過的,則不會標記。

3.回收

有標記的對象稱爲可達對象,沒有標記的對象稱爲不可達對象。不可達對象就是將被回收的垃圾。clr將回收這一部分不可達對象。

4.搬遷、壓縮

垃圾回收後,佔用的內存被釋放,clr將可達對象搬遷到這裏以壓縮堆。

5.修改引用

搬遷可達對象後,所有指向這些可達對象的引用將失效,GC會重新遍歷程序的所有跟,來修改引用。

如果各個線程正在執行,很可能導致變量引用到無效的對象地址,所以整個進程的正在執行託管代碼的線程是被掛起的。

6.線程掛起算法

掛起時,CLR會記錄每個線程的指令指針以確定線程當前執行到哪裏以便將來在垃圾回收結束後進行恢復。

如果一個線程的指令指針恰好到達了一個安全點,則可以掛起該線程,否則CLR會嘗試劫持該線程,如果還未到達安全點,則等待幾百毫秒後CLR會嘗試再一次劫持該線程,有可能經過多次嘗試,最終掛起該線程,噹噹前進程的所有執行託管代碼的線程都掛起後,垃圾回收器就可以開始工作了。


博客園有大神的文章寫得非常非常詳細:

http://www.cnblogs.com/solan/archive/2012/08/24/CSharp11.html

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