C# GC垃圾回收簡述



C# GC垃圾回收簡述


首先:談談託管,什麼叫託管,我的理解就是託付C# 運行環境幫我們去管理,在這個運行環境中可以幫助我們開闢內存和釋放內存,開闢內存一般用new ,內存是隨機分配的,釋放主要靠的是GC 也就是垃圾回收機制。哪麼有兩個大問題 1.GC 可以回收任何對象嗎?2.GC 什麼時候來回收對象?回收那些對象?

對於第一個問題,GC 可以回收任何對象嗎?我是這樣理解的,首先要明白一點,C# 在強大也管不到非託管代碼?哪麼什麼是非託管代碼呢?比如stream (文件),connection (數據庫連接),COM (組件)等等。。哪麼這些對象是需要進行連接的,比如說我們寫這樣一句話FileStream fs = new FileStream(“d://a.txt”,FileMode.Open); 實際上已經創建了和d://a.txt 的連接,如果重複兩次就會報錯。哪麼fs 這個對象叫做非託管對象,也就是說C#

不能自動去釋放和d://a.txt 的連接。哪麼對於非託管的代碼怎麼辦,一會我來說。

    對於第二個問題,GC 什麼時候來回收,回收什麼對象?我想後面的就不用我說了,當然是回收託管對象了。但是GC 什麼時候回收?是這樣的:GC 是隨機的,沒有人知道他什麼時候來,哪麼我寫了一個例子,證明這一點

private void button1_Click(object sender, EventArgs e)

{           

AA a = new AA ();

AA b = new AA ();

AA c = new AA ();

AA d = new AA ();

 

}

public class AA{}

在講這個例子之前,要明白什麼被稱之爲垃圾,垃圾就是一個內存區域,沒有被任何引用指向,或者不再會被用到。 哪麼在第一次點擊按鈕的時候會生成4 個對象,第二次點擊按鈕的時候也會生成4 個對象,但是第一次生成的4 個對象就已經是垃圾了,因爲,第一次生成的4 個對象隨着 button1_Click 函數的結束而不會再被調用(或者說不能再被調用 ),哪麼這個時候GC 就會來回收嗎?不是的!我說了GC 是隨機的,哪麼你只管點你的,不一會GC 就會來回收的(這裏我們可以認爲,內存中存在一定數量的垃圾之後,GC 會來 ,要證明GC 來過我們把AA 類改成

public class AA

{

~AA()

{

        MessageBox .Show(" 析構函數被執行了" );

}

}

要明白,GC 清理垃圾,實際上是調用析構函數,但是這些代碼是託管代碼(因爲裏面沒有涉及到SteamConnection 等。。)所以在析構函數中,我們可以只寫一個MsgBox 來證明剛的想法;這個時候,運行你的程序,一直點擊按鈕,不一會就會出現一大堆的“ 析構函數被執行了”

 

    好了,然後讓我們看看能不能改變GC 這種爲所欲爲的天性,答案是可以的,我們可以通過調用GC.Collect(); 來強制GC 進行垃圾回收,哪麼button1_Click 修改如下

private void button1_Click(object sender, EventArgs e)

{           

AA a = new AA ();

AA b = new AA ();

AA c = new AA ();

AA d = new AA ();

GC .Collect();

}

哪麼在點擊第一次按鈕的時候,生成四個對象,然後強制垃圾回收,這個時候,會回收嗎?當然不會,因爲,這四個對象還在執行中(方法還沒結束),當點第二次按鈕的時候,會出現四次" 析構函數被執行了" 這是在釋放第一次點擊按鈕的四個對象,然後以後每次點擊都會出現四次" 析構函數被執行了" ,哪麼最後一次的對象什麼時候釋放的,在關閉程序的時候釋放(因爲關閉程序要釋放所有的內存)。

 

好了,現在來談談非託管代碼,剛纔說過,非託管代碼不能由垃圾回收釋放,我們把AA 類改成如下

public class AA

{

     FileStream fs = new FileStream ("D://a.txt" ,FileMode .Open);

     ~AA()

     {

            MessageBox .Show(" 析構函數被執行了" );

  }

}

private void button1_Click(object sender, EventArgs e)

{

            AA a = new AA ();

}

如果是這樣一種情況,哪麼第二次點擊的時候就會報錯,原因是一個文件只能創建一個連接。哪麼一定要釋放掉第一個資源,纔可以進行第二次的連接。哪麼首先我們想到用 GC .Collect() ,來強制釋放閒置的資源,修改代碼如下:

private void button1_Click(object sender, EventArgs e)

{

            GC .Collect();

            AA a = new AA ();

}

哪麼可以看到,第二次點按鈕的時候,確實出現了“析構函數被執行了“, 但是程序仍然錯了,原因前面我說過,因爲Stream 不是託管代碼,所以C# 不能幫我們回收,哪怎麼辦?

自己寫一個Dispose 方法;去釋放我們的內存。代碼如下:

public class AA :IDisposable

    {

        FileStream fs = new FileStream ("D://a.txt" ,FileMode .Open);

        ~AA()

        {

            MessageBox .Show(" 析構函數被執行了" );

 

        }

 

        #region IDisposable 成員

 

        public void Dispose()

        {

            fs.Dispose();

            MessageBox .Show("dispose 執行了" );

        }

 

        #endregion

    }

好了,我們看到了,繼承 IDisposable 接口以後會有一個Dispose 方法(當然了,你不想繼承也可以,但是接口給我們提供一種規則,你不願意遵守這個規則,就永遠無法融入整個團隊,你的代碼只有你一個人能看懂),好了閒話不說,這樣一來我們的 button1_Click 改爲private void button1_Click(object sender, EventArgs e)

{

            AA a = new AA ();

a.Dispose();

}

我們每次點擊之後,都會發現執行了“ dispose 執行了”,在關閉程序的時候仍然執行了“析構函數被執行了”這意味了,GC 還是工作了,哪麼如果程序改爲:

private void button1_Click(object sender, EventArgs e)

{

            AA a = new AA ();

a.Dispose();

GC .Collect();

}

每次都既有“ dispose 執行了又有”“析構函數被執行了”,這意味着GC 又來搗亂了,哪麼像這樣包含Stream connection 的對象,就不用GC 來清理了,只需要我們加上最後一句話 GC.SuppressFinalize(this) 來告訴GC ,讓它不用再調用對象的析構函數中。 那麼改寫後的AAdispose 方法如下:

 

        public void Dispose()

        {

            fs.Dispose();

            MessageBox .Show("dispose 執行了" );

GC.SuppressFinalize(this);

        }

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