轉載 2011年03月16日 17:21:00
今天來談談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清理垃圾,實際上是調用析構函數,但是這些代碼是托管代碼(因為里面沒有涉及到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();
}
哪么在點擊第一次按鈕的時候,生成四個對象,然后強制垃圾回收,這個時候,會回收嗎?當然不會,因為,這四個對象還在執行中(方法還沒結束),當點第二次按鈕的時候,會出現四次"析構函數被執行了",這是在釋放第一次點擊按鈕的四個對象,然后以后每次點擊都會出現四次"析構函數被執行了",哪么最后一次的對象什么時候釋放的,在關閉程序的時候釋放(因為關閉程序要釋放所有的內存)。
好了,現在來談談非托管代碼,剛才說過,非托管代碼不能由垃圾回收釋放,我們把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,讓它不用再調用對象的析構函數中。那么改寫后的AA的dispose方法如下:
public void Dispose()
{
fs.Dispose();
MessageBox.Show("dispose執行了");
GC.SuppressFinalize(this);
}
本文下載自CSDN下載頻道。
-------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
那.Net程序員在編程時應該怎么做,有沒有一種既簡單又有有效的方法來處理內在回收。愚人作以下建議,望各路高手不吝賜教:
1,對於不包涵或沒有引用(直接或間接)非托管資源的類,特別是作用如同Struct的實體類,析構、終結器、Dispose均不采用。
2,對於包涵非托管資源的類,如數據庫連接對象,文件句柄等,應繼承IDispose接口,在Dispose方法中清理非托管對象。客戶代碼用using(…){}格式顯示調用Dispose。如果繼承了IDispose接口,Dispose方法就不要留空,這樣沒有任何意義。除了構造器,任何方法體留空都有害無益。
3,所有自定義類一般均不建議顯式聲明析構函數、Finalize方法。