.net平臺下垃圾回收機制

引言:使用c++進行編程,內存的處理絕對是讓每一個程序設計者最頭疼的一塊了。可是對於.net平臺下使用c#語言開發系統,內存管理能夠說已經不算是問題了。在.net平臺下CLR負責管理內存,CLR中的垃圾收集器GC:Garbage Collection,負責執行內存的清理工做,可是GC也只是負責清理託管堆上的垃圾對象,而對於非託管的資源對象,GC則不起做用,必需要程序開發者手動清理。此處須要稍微說明:通常而言,非託管資源主要包括數據庫連接、文件句柄、COM對象、套接字、GDI+對象、互斥體等等。c++

 

在介紹GC前,有必要對.net中CLR管理內存區域作簡要介紹:算法

  一、 堆棧:用於分配值類型實例。堆棧主要操做系統管理,而不受垃圾收集器的控制,當值類型實例所在方法結束時,其存儲單位自動釋放。棧的執行效率高,但存儲容量有限。數據庫

  2 、GC堆:用於分配小對象實例。若是引用類型對象實例的大小小於85000字節,實例將被配置在GC堆上,當有內存分配或者回收時,垃圾收集器可能會對GC堆進行壓縮。編程

  三、 LOH:large object heap,用於分配大對象實例。若是引用類型對象的實例的大小不小於85000字節時,該實例將被分配到LOH堆上,而LOH堆不會被壓縮,並且只在徹底GC回收時被回收。c#

  

  既然要清理垃圾,那麼必然要明白什麼是垃圾吧,垃圾的理解:一個對象成爲「垃圾」表示該對象不被任何其餘對象所引用。所以GC必須採用必定的算法在託管堆中遍歷全部對象,最終造成一個可達對象圖,而不可達的對象將成爲被釋放的垃圾對象等待收集。性能優化

  在明白了什麼是垃圾後,確定會對GC如何回收垃圾提出疑問。.net平臺下,每一個應用程序都有一組根(指針),它指向託管堆中的存儲位置,由JIT編譯器和CLR運行時維護根指針列表,主要包括全局變量、靜態變量、局部變量和寄存器指針等。GC正是經過根指針列表來得到託管堆中的對象圖,其中定義了應用程序根引用的託管堆中的對象,當GC啓動時,它假設全部對象都是可回收的垃圾,開始遍歷全部的根,將根引用的對象標記爲可達對象添加到可達對象圖中,在遍歷過程當中,若是根引用的對象還引用着其餘對象,則該對象也被添加到可達對象圖中,依次類推,GC經過根列表的遞歸遍歷,將能找到全部可達對象,並造成一個可達對象圖。同時那些不可達對象則被認爲是可回收對象,GC接着運行垃圾收集進程來釋放垃圾對象的內存空間。這種收集算法稱爲:標記和清除收集算法。ide

 

垃圾回收通常在下列狀況下進行:函數

1 內存不足溢出時,更確切的應該說是第0代對象充滿時。性能

2 調用GC.Collect方法強制執行垃圾回收。(通常不要執行此方法)優化

3 Windows報告內存不足時,CLR將強制執行垃圾回收。

4 CLR卸載AppDomain時,GC將對全部代齡的對象執行垃圾回收。

5 其餘狀況,如物理內存不足,超出短時間存活代的內存段門限,運行主機拒絕分配內存等。

 

垃圾回收運行機制:

  垃圾收集器將託管堆中對象分爲三代:0、1和2,在CLR初始化時,會選擇爲三代設置不一樣的闕值容量,通常爲:第0代大約爲256KB,第1代2MB,第2代10MB。容量越大效率越低,而GC收集器會自動調節其闕值容量來提高執行效率。在CLR初始化後,首先添加到託管堆中的對象都被定位第0代對象,當有垃圾回收執行時,未被回收的對象代齡將提高一級,變成第1代對象,然後新建對象仍未第0代對象。代齡越小表示對象越新,一般狀況下其生命週期也最短,所以GC老是先收集第0代的不可達對象內存。

  隨着對象的不斷建立,垃圾收集再次啓動時則只會檢查0代對象並回收0代垃圾對象。而1代對象因爲未達到1代容量闕值,則不會進行垃圾回收操做,從而有效地提升了垃圾收集的效率,而這也是代齡機制在垃圾回收中的性能優化做用。當第0代對象釋放的內存不足以建立新的對象,同時1代對象的體積也超出了容量闕值是,垃圾收集器將同時對0代和1代對象進行垃圾回收。回收以後,未被回收的1代對象變化2級對象,未被回收的0代對象升級爲1代對象,然後新建的對象仍爲第0代對象。

 

注:微軟強烈建議不要經過GC.Collect方法來強制執行垃圾收集,這樣會妨礙GC自己的工做方式,經過Collect會使對象代齡不斷提高,擾亂應用程序的內存使用。只有在明確知道有大量對象中止引用時,才考慮使用GC.Collect方法來調用收集器。

 

上面介紹了垃圾管理器GC清理託管資源所涉及的一些機理,然而對於非託管資源,須要開發者手動清理,方法主要有:Finalize方法和Dispose方法。

Finalize:

Finalize方法又稱爲終止化操做:經過對自定義類型實現一個Finalize方法來釋放非託管資源,而終止化操做在對象的內存回收以前經過調用Finalize方法來釋放資源。在析構函數中重寫Finalize方法,當垃圾管理器啓動時,對於斷定爲可回收的垃圾對象,GC會自動執行其Finalize方法清理非託管資源。

複製代碼
protected override void Finalize()
        {
            try
            { 
                //執行自定義資源清理操做
            }
            finally
            {
                base.Finalize();
            }
        }
複製代碼

Finalize的缺點是:

終止化操做的時間沒法控制,執行順序也不能保證。

Finalize方法會極大的損失性能,GC使用一個終止話隊列的內部結構來跟蹤具備Finalize方法的對象。

重寫finalize方法的類型對象,其引用類型對象的代齡將被提高,帶來內存壓力。

 

Dispose:

Dispose模式的實現是:定義的類型必須實現System.IDisposable接口,該接口中定義了一個公有無參數的Dispose方法,程序設計者能夠在Dispose方法中實現對非託管資源的清理工做。

下面編寫一個項目中遇到使用Dispose方法的例子,功能是在套接字使用完畢後釋放資源

複製代碼
public class SocketConnection : IDisposable
    {
        //邏輯操做
        //.....................

        //實現Dispose
        public void Dispose()
        {
            try
            {
                this.ClientSock.Shutdown(SocketShutdown.Both);
                this.ClientSock.Close();
                this.Server = null;
            }
            catch (Exception ex)
            { }
        }
    }
複製代碼

 

總結:

在.net中,在堆棧上分配的資源在調用結束後,其內存自動會釋放。

託管堆中的資源,由CLR的垃圾管理器進行清理操做。

對於非託管資源,必須由程序設計者進行操做,而對於Finalize和Dispose,最好採用Dispose方法。

相關文章
相關標籤/搜索