標籤:
今天來談談C# 的GC ,也就是記憶體回收機制,非常的受教,總結如下
首先:談談託管,什麼叫託管,我的理解就是託付C# 運行環境幫我們去管理,在這個運行環境中可以協助我們開闢記憶體和釋放記憶體,開闢記憶體一般用new ,記憶體是隨機分配的,釋放主要靠的是GC 也就是記憶體回收機制。哪麼有兩個大問題 1.GC 可以回收任何對象嗎?2.GC 什麼時候來回收對象?回收那些對象?
對於第一個問題,GC 可以回收任何對象嗎?我是這樣理解的,首先要明白一點,C# 在強大也管不到Unmanaged 程式碼?哪麼什麼是Unmanaged 程式碼呢?比如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 清理垃圾,實際上是調用解構函式,但是這些代碼是Managed 程式碼(因為裡面沒有涉及到Steam ,Connection 等。。)所以在解構函式中,我們可以唯寫一個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();
}
哪麼在點擊第一次按鈕的時候,產生四個對象,然後強制記憶體回收,這個時候,會回收嗎?當然不會,因為,這四個對象還在執行中(方法還沒結束),當點第二次按鈕的時候,會出現四次" 解構函式被執行了" , 這是在釋放第一次點擊按鈕的四個對象,然後以後每次點擊都會出現四次" 解構函式被執行了" ,哪麼最後一次的對象什麼時候釋放的,在關閉程式的時候釋放(因為關閉程式要釋放所有的記憶體)。
好了,現在來談談Unmanaged 程式碼,剛才說過,Unmanaged 程式碼不能由記憶體回收釋放,我們把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 不是Managed 程式碼,所以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 ,讓它不用再調用對象的解構函式中。 那麼改寫後的AA 的dispose 方法如下:
public void Dispose()
{
fs.Dispose();
MessageBox .Show("dispose 執行了" );
GC.SuppressFinalize(this);
}
C# GC 記憶體回收機制