GC 垃圾回收
一、托管
.Net所指的托管資源到底是什么意思呢?是相對于所有資源,還是只限于某一方面的資源?很多人對此不是很了解。
其實(shí).Net所指的托管只是針對內(nèi)存這一個方面,并不是對于所有的元素;因此對于Stream,數(shù)據(jù)庫的連接GDI+的相關(guān)對象,還有Com對象等等,這些資源并不是受到.Net管理而統(tǒng)稱為非托管資源。而對于內(nèi)存的釋放和回收,系統(tǒng)提供了GC(Garbage Collector),而至于其他資源則需要手動進(jìn)行釋放。
二、垃圾
什么是垃圾。.Net類型分為兩大類,一個就是值類型,另一個就是引用類型。前者是分配在棧上,并不需要GC回收;后者是分配在堆上,因此它的內(nèi)存釋放和回收需要通過GC來完成。GC的全程為"Garbage Collector",顧名思義就是垃圾回收器,那么只有被稱為垃圾的對象才能被GC回收。也就是說,一個引用類型對象所占的內(nèi)存需要被GC回收,而滿足回收的條件,首先就要需要稱為垃圾。那么.Net如果判定一個引用類型對象是垃圾呢,.Net的判斷很簡單,只要判定此對象或者其包含的子對象沒有任何引用是有效的,那么系統(tǒng)就認(rèn)為它是垃圾。
三、GC運(yùn)作方式
明確了基本概念,接下來就說說GC的運(yùn)作方式以及GC的功能,內(nèi)存的釋放和回收需要伴隨著程序的運(yùn)行,因此系統(tǒng)為GC安排了獨(dú)立的線程。那么GC的工作大致是,查詢內(nèi)存中對象是否成為垃圾,然后對垃圾進(jìn)行釋放和回收。那么對于GC對于內(nèi)存回收采取了一定的有限算法進(jìn)行輪詢回收內(nèi)存資源。其次,對于內(nèi)存中的垃圾分為兩種,一種需要調(diào)用對象的析構(gòu)函數(shù),另一種是不需要調(diào)用的。GC對于前者(需要調(diào)用析構(gòu)函數(shù)的)的回收需要通過兩步完成,第一步是調(diào)用對象的析構(gòu)函數(shù),第二步是回收內(nèi)存,但是要注意這兩步不是在GC一次輪詢完成,即需要兩次輪詢;相對于后者(不需要調(diào)用析構(gòu)函數(shù)的),則只是回收內(nèi)存而已。
對于某個具體的資源,是無法確切知道對象析構(gòu)函數(shù)什么時候被調(diào)用的,以及GC什么時候會去釋放和回收它所占用的內(nèi)存。那么對于C、C++之類語言轉(zhuǎn)換過來的程序員來說,這里需要轉(zhuǎn)變觀念。
對于程序資源來說,我們應(yīng)該做些什么,以及如何去做,才能使程序效率最高,同時占用資源能盡快的釋放。前面說過托管資源分兩種,托管的內(nèi)存資源,這是不需要我們操心的,系統(tǒng)已經(jīng)為我們進(jìn)行管理了,那么對于非托管資源,這里再重申一下,這就是Stream,數(shù)據(jù)庫的連接,GDI+的相關(guān)對象,還有Com的相關(guān)對象,還有Com對象等等這些資源,需要我們手動去釋放。
如何去釋放,應(yīng)該把這些操作放到哪里比較好呢。.Net提供可三種方法,也是最常見的三種,大致如下
1、析構(gòu)函數(shù)
2、繼承IDisposable接口,實(shí)現(xiàn)Dispose方法;
3、提供Close方法。
經(jīng)過前面的介紹,可以知道析構(gòu)函數(shù)只能被GC來調(diào)用,那么無法確定它什么時候被調(diào)用,因此用它作為資源的釋放并不是很合理,因?yàn)橘Y源釋放不及時;但是為了防止資源泄露,畢竟它會被GC調(diào)用,因此析構(gòu)函數(shù)可以作為一個補(bǔ)救方法。而Close與Dispose這兩種方法的區(qū)別在于,調(diào)用完了對象的Close方法后,此對象由可能被重新進(jìn)行使用;而Dispose方法來說,此對象所占用的資源需要被標(biāo)記無用了,也就是此對象被銷毀了,等待GC回收,不能再被使用。
例如,常見SqlConnection這個類,當(dāng)調(diào)用完Close方法后,可以通過Open重新打開數(shù)據(jù)庫連接,當(dāng)徹底不用這個對象了就可以調(diào)用Dispose方法來標(biāo)記此對象無用,等待GC回收。明白了這兩種方法的意思后,大家在往自己的類中添加的接口時候,不要歪曲了這兩者意思。
接下來說說這三個函數(shù)的調(diào)用時機(jī),我用幾個試驗(yàn)結(jié)果來進(jìn)行說明,可能會使大家的印象更深。
首先是這三種方法的實(shí)現(xiàn),大致如下:
public class DisposeClass : IDisposable{public void Close(){Debug.WriteLine("Close called!");}~DisposeClass(){Debug.WriteLine("Destructor called!");}#region IDisposable Memberspublic void Dispose(){Debug.WriteLine("Dispose called!");}#endregion}對于Close來說不屬于真正意義上的釋放,除了注意它需要顯示被調(diào)用外,我在此對它不多說了。而對于析構(gòu)函數(shù)而言,不是在對象離開作用域后立刻被執(zhí)行,只有在關(guān)閉進(jìn)程或者調(diào)用GC.Collect方法的時候才被調(diào)用,參看如下的代碼運(yùn)行結(jié)果。
namespace ConsoleApplication1 {public class Program{static void Main(string[] args){// Show destructornew 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 Memberspublic void Dispose(){Debug.WriteLine("Dispose called!");}#endregion} }這時在Visual Studio的輸出標(biāo)簽里輸出:
After created!
Destructor called!
顯然在出了Create函數(shù)外,myClass對象的析構(gòu)函數(shù)沒有被立刻調(diào)用,而是等顯示調(diào)用GC.Collect才被調(diào)用。
對于Dispose來說,也需要顯示的調(diào)用,但是對于繼承了IDisposable的類型對象可以使用using這個關(guān)鍵字,這樣對象的Dispose方法在出了using范圍后會被自動調(diào)用。例如:
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 Memberspublic void Dispose(){Debug.WriteLine("Dispose called!");}#endregion} }在Visual Studio里輸出:
Dispose called!
那么對于如上DisposeClass類型的Dispose實(shí)現(xiàn)來說,事實(shí)上GC還需要調(diào)用對象的析構(gòu)函數(shù),按照前面的GC流程來說,GC對于需要調(diào)用析構(gòu)函數(shù)的對象來說,至少經(jīng)過兩個步驟,即首先調(diào)用對象的析構(gòu)函數(shù),其次回收內(nèi)存。也就是說,按照上面所寫的Dispose函數(shù),雖說被執(zhí)行了,但是GC還是需要執(zhí)行析構(gòu)函數(shù),那么一個完整的Dispose函數(shù),應(yīng)該通過調(diào)用GC.SuppressFinalize(this )來告訴GC,讓它不用再調(diào)用對象的析構(gòu)函數(shù)中。那么改寫后的DisposeClass如下:
public class DisposeClass : IDisposable{public void Close(){Debug.WriteLine("Close called!");}~DisposeClass(){Debug.WriteLine("Destructor called!");}#region IDisposable Memberspublic void Dispose(){Debug.WriteLine("Dispose called!");//不在執(zhí)行析構(gòu)函數(shù)GC.SuppressFinalize( this );}#endregion}對以上代碼進(jìn)行測試:
private void Run(){using( DisposeClass myClass = new DisposeClass() ){//other operation here }}private void CallGC(){GC.Collect();}// Show destructor Run();Debug.WriteLine( "After Run!" );CallGC();輸出:
Dispose?called!
After?Run!
對比表格如下:
| ? | 析構(gòu)函數(shù) | Dispose方法 | Close方法 |
| 意義 | 銷毀對象 | 銷毀對象 | 關(guān)閉對象資源 |
| 調(diào)用方式 | 不能被顯式調(diào)用,會被GC調(diào)用 | 需要顯式調(diào)用 或者通過using語句 | 需要顯式調(diào)用 |
| 調(diào)用時機(jī) | 不確定 | 確定,在顯式調(diào)用或者離開using程序塊 | 確定,在顯式調(diào)用時 ? |
那么在定義一個類型的時候,是否一定要給出這三個函數(shù)地實(shí)現(xiàn)呢。
我的建議大致如下。
1.提供析構(gòu)函數(shù),避免資源未被釋放,主要是指非內(nèi)存資源;
2.對于Dispose和Close方法來說,需要看所定義的類型所使用的資源(參看前面所說),而決定是否去定義這兩個函數(shù);
3.在實(shí)現(xiàn)Dispose方法的時候,一定要加上“GC.SuppressFinalize( this )”語句,避免再讓GC調(diào)用對象的析構(gòu)函數(shù)。
2013-5-16
析構(gòu)函數(shù)只能由垃圾回收器調(diào)用。
?
Despose()方法只能由類的使用者調(diào)用。
在C#中,凡是繼承了IDisposable接口的類,都可以使用using語句,從而在超出作用域后,讓系統(tǒng)自動調(diào)用Dispose()方法。
一個資源安全的類,都實(shí)現(xiàn)了IDisposable接口和析構(gòu)函數(shù)。提供手動釋放資源和系統(tǒng)自動釋放資源的雙保險。
總結(jié)
- 上一篇: spring配置文件路径修改后web.x
- 下一篇: 阿里云服务器问题攻略