C#基礎知識之GC 垃圾回收


一、托管

  .Net所指的托管資源到底是什么意思呢?是相對於所有資源,還是只限於某一方面的資源?很多人對此不是很了解。

  其實.Net所指的托管只是針對內存這一個方面,並不是對於所有的元素;因此對於Stream,數據庫的連接GDI+的相關對象,還有Com對象等等,這些資源並不是受到.Net管理而統稱為非托管資源。而對於內存的釋放和回收,系統提供了GC(Garbage Collector),而至於其他資源則需要手動進行釋放。

二、垃圾

  什么是垃圾。.Net類型分為兩大類,一個就是值類型,另一個就是引用類型。前者是分配在棧上,並不需要GC回收后者是分配在堆上,因此它的內存釋放和回收需要通過GC來完成。GC的全程為"Garbage Collector",顧名思義就是垃圾回收器,那么只有被稱為垃圾的對象才能被GC回收。也就是說,一個引用類型對象所占的內存需要被GC回收,而滿足回收的條件,首先就要需要稱為垃圾。那么.Net如果判定一個引用類型對象是垃圾呢,.Net的判斷很簡單,只要判定此對象或者其包含的子對象沒有任何引用是有效的,那么系統就認為它是垃圾。

三、GC運作方式

  明確了基本概念,接下來就說說GC的運作方式以及GC的功能,內存的釋放和回收需要伴隨着程序的運行,因此系統為GC安排了獨立的線程。那么GC的工作大致是,查詢內存中對象是否成為垃圾,然后對垃圾進行釋放和回收。那么對於GC對於內存回收采取了一定的有限算法進行輪詢回收內存資源。其次,對於內存中的垃圾分為兩種,一種需要調用對象的析構函數,另一種是不需要調用的。GC對於前者(需要調用析構函數的)的回收需要通過兩步完成,第一步是調用對象的析構函數,第二步是回收內存,但是要注意這兩步不是在GC一次輪詢完成,即需要兩次輪詢;相對於后者(不需要調用析構函數的),則只是回收內存而已。

  對於某個具體的資源,是無法確切知道對象析構函數什么時候被調用的,以及GC什么時候會去釋放和回收它所占用的內存。那么對於C、C++之類語言轉換過來的程序員來說,這里需要轉變觀念。

對於程序資源來說,我們應該做些什么,以及如何去做,才能使程序效率最高,同時占用資源能盡快的釋放。前面說過托管資源分兩種,托管的內存資源,這是不需要我們操心的,系統已經為我們進行管理了,那么對於非托管資源,這里再重申一下,這就是Stream,數據庫的連接,GDI+的相關對象,還有Com的相關對象,還有Com對象等等這些資源,需要我們手動去釋放。

如何去釋放,應該把這些操作放到哪里比較好呢。.Net提供可三種方法,也是最常見的三種,大致如下

  1、析構函數

  2、繼承IDisposable接口,實現Dispose方法;

  3、提供Close方法。

  經過前面的介紹,可以知道析構函數只能被GC來調用,那么無法確定它什么時候被調用,因此用它作為資源的釋放並不是很合理,因為資源釋放不及時;但是為了防止資源泄露,畢竟它會被GC調用,因此析構函數可以作為一個補救方法。而Close與Dispose這兩種方法的區別在於,調用完了對象的Close方法后,此對象有可能被重新進行使用;而Dispose方法來說,此對象所占用的資源需要被標記無用了,也就是此對象被銷毀了,等待GC回收,不能再被使用。

  例如,常見SqlConnection這個類,當調用完Close方法后,可以通過Open重新打開數據庫連接,當徹底不用這個對象了就可以調用Dispose方法來標記此對象無用,等待GC回收。明白了這兩種方法的意思后,大家在往自己的類中添加的接口時候,不要歪曲了這兩者意思。

接下來說說這三個函數的調用時機,我用幾個試驗結果來進行說明,可能會使大家的印象更深。

首先是這三種方法的實現,大致如下:

public class DisposeClass : IDisposable
{
    public void Close()
    {
        Debug.WriteLine("Close called!");
    }
    ~DisposeClass()
    {
        Debug.WriteLine("Destructor called!");
    }
    #region IDisposable Members
    public void Dispose()
    {
        Debug.WriteLine("Dispose called!");
    }
    #endregion
}    
View Code

 

  對於Close來說不屬於真正意義上的釋放,除了注意它需要顯示被調用外,我在此對它不多說了。而對於析構函數而言,不是在對象離開作用域后立刻被執行,只有在關閉進程或者調用GC.Collect方法的時候才被調用,參看如下的代碼運行結果。

namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            // Show destructor
           new Program().Create();
           Debug.WriteLine("After created!");
          new Program().CallGC();
          Console.ReadKey();
        }
        private void Create()
        {
            DisposeClass myClass = new DisposeClass();
        }
        private void CallGC()
        {
            GC.Collect();
        }
    }
    public class DisposeClass : IDisposable
    {
        public void Close()
        {
            Debug.WriteLine("Close called!");
        }
        ~DisposeClass()
        {
            Debug.WriteLine("Destructor called!");
        }
        #region IDisposable Members
        public void Dispose()
        {
            Debug.WriteLine("Dispose called!");
        }
        #endregion
    }
}
View Code

 

這時在Visual Studio的輸出標簽里輸出:

  

 

 

顯然在執行完Create函數外,myClass對象的析構函數沒有被立刻調用,而是等顯示調用GC.Collect才被調用。

對於Dispose來說,也需要顯示的調用,但是對於繼承了IDisposable的類型對象可以使用using這個關鍵字,這樣對象的Dispose方法在出了using范圍后會被自動調用。例如:

namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            using (DisposeClass myClass = new DisposeClass())
            {
                //other operation here
           }
           Console.ReadKey();
        }
    }


    public class DisposeClass : IDisposable
    {
        public void Close()
        {
            Debug.WriteLine("Close called!");
        }

        ~DisposeClass()
        {
            Debug.WriteLine("Destructor called!");
        }
        #region IDisposable Members
        public void Dispose()
        {
            Debug.WriteLine("Dispose called!");
        }
        #endregion
    }
}
View Code

 

在Visual Studio里輸出:

Dispose called!

Destructor called!

那么對於如上DisposeClass類型的Dispose實現來說,事實上GC還需要調用對象的析構函數,正常來說Dispose函數,應該通過調用GC.SuppressFinalize(this )來告訴GC,讓它不用再調用對象的析構函數中。那么改寫后的DisposeClass如下:

public class DisposeClass : IDisposable
{
    public void Close()
    {
        Debug.WriteLine("Close called!");
    }
    ~DisposeClass()
    {
        Debug.WriteLine("Destructor called!");
    }
    #region IDisposable Members
    public void Dispose()
    {
        Debug.WriteLine("Dispose called!");
        //不在執行析構函數
        GC.SuppressFinalize( this );
    }
    #endregion
} 
View Code

 

對比表格如下:

 

析構函數

Dispose方法

Close方法

意義

銷毀對象

銷毀對象

關閉對象資源

調用方式

不能被顯式調用,會被GC調用

需要顯式調用

或者通過using語句

需要顯式調用

調用時機

不確定

確定,在顯式調用或者離開using程序塊

確定,在顯式調用時

 

那么在定義一個類型的時候,是否一定要給出這三個函數地實現呢。我的建議大致如下。

1.提供析構函數,避免資源未被釋放,主要是指非內存資源;

2.對於Dispose和Close方法來說,需要看所定義的類型所使用的資源(參看前面所說),而決定是否去定義這兩個函數;

3.在實現Dispose方法的時候,一定要加上“GC.SuppressFinalize( this )”語句,避免再讓GC調用對象的析構函數。

 

注意事項:析構函數只能由垃圾回收器調用。Despose()方法只能由類的使用者調用。在C#中,凡是繼承了IDisposable接口的類,都可以使用using語句,從而在超出作用域后,讓系統自動調用Dispose()方法。一個資源安全的類,都實現了IDisposable接口和析構函數。提供手動釋放資源和系統自動釋放資源的雙保險。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM