淺談C#託管程式中的資源釋放問題
便於對文章的開展,需要先明確兩個概念。
第一個就是很多人用.Net寫程式,會談到託管這個概念。那麼.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什麼時候會去釋放和回收它所佔用的記憶體。那麼對於從C、C++之類語言轉換過來的程式設計師來說,這裡需要轉變觀念。
那麼對於程式資源來說,我們應該做些什麼,以及如何去做,才能使程式效率最高,同時佔用資源能儘快的釋放。前面也說了,資源分為兩種,託管的記憶體資源,這是不需要我們操心的,系統已經為我們進行管理了
;那麼對於非託管的資源,這裡再重申一下,就是Stream,資料庫的連線,GDI+的相關物件,還有Com物件等等這些資源,需要我們手動去釋放。
如何去釋放,應該把這些操作放到哪裡比較好呢。.Net提供了三種方法,也是最常見的三種,大致如下:
1.解構函式;
2.繼承IDisposable介面,實現Dispose方法;
3.提供Close方法。
經過前面的介紹,可以知道解構函式只能被GC來呼叫的,那麼無法確定它什麼時候被呼叫,因此用它作為資源的釋放並不是很合理,因為資源釋放不及時;但是為了防止資源洩漏,畢竟它會被GC呼叫,因此解構函式可以作為一個補救方法。而Close與Dispose這兩種方法的區別在於,呼叫完了物件的Close方法後,此物件有可能被重新進行使用;而Dispose方法來說,此物件所佔有的資源需要被標記為無用了,也就是此物件被銷燬了,不能再被使用。
例
如,常見SqlConnection這個類,當呼叫完Close方法後,可以通過Open重新開啟資料庫連線,當徹底不用這個物件了就可以呼叫
Dispose方法來標記此物件無用,等待GC回收。明白了這兩種方法的意思後,大家在往自己的類中新增的介面時候,不要歪曲了這兩者意思。
接下來說說這三個函式的呼叫時機,我用幾個試驗結果來進行說明,可能會使大家的印象更深。
首先是這三種方法的實現,大致如下:
/// <summary>
/// The class to show three disposal function
/// </summary>
public class DisposeClass:IDisposable
{
public void Close()
{
Debug.WriteLine( "Close called!" );
}
~DisposeClass()
{
Debug.WriteLine( "Destructor called!" );
}
#region IDisposable Members
public void Dispose()
{
// TODO: Add DisposeClass.Dispose implementation
Debug.WriteLine( "Dispose called!" );
}
#endregion
}
對於Close來說不屬於真正意義上的釋放,除了注意它需要顯示被呼叫外,我在此對它不多說了。而對於解構函式而言,不是在物件離開作用域後立刻被執行,只有在關閉程式或者呼叫GC.Collect方法的時候才被呼叫,參看如下的程式碼執行結果。
private void Create()
{
DisposeClass myClass = new DisposeClass();
}
private void CallGC()
{
GC.Collect();
}
// Show destructor
Create();
Debug.WriteLine( "After created!" );
CallGC();
執行的結果為:
After created!
Destructor called!
顯然在出了Create函式外,myClass物件的解構函式沒有被立刻呼叫,而是等顯示呼叫GC.Collect才被呼叫。
對於Dispose來說,也需要顯示的呼叫,但是對於繼承了IDisposable的型別物件可以使用using這個關鍵字,這樣物件的Dispose方法在出了using範圍後會被自動呼叫。例如:
using( DisposeClass myClass = new DisposeClass() )
{
//other operation here
}
如上執行的結果如下:
Dispose called!
那麼對於如上DisposeClass型別的Dispose實現來說,事實上GC還需要呼叫物件的解構函式,按照前面的GC流程來說,GC對於需要呼叫析
構函式的物件來說,至少經過兩個步驟,即首先呼叫物件的解構函式,其次回收記憶體。也就是說,按照上面所寫的Dispose函式,雖說被執行了,但是GC還
是需要執行解構函式,那麼一個完整的Dispose函式,應該通過呼叫GC.SuppressFinalize(this
)來告訴GC,讓它不用再呼叫物件的解構函式中。那麼改寫後的DisposeClass如下:
/// <summary>
/// The class to show three disposal function
/// </summary>
public class DisposeClass:IDisposable
{
public void Close()
{
Debug.WriteLine( "Close called!" );
}
~DisposeClass()
{
Debug.WriteLine( "Destructor called!" );
}
#region IDisposable Members
public void Dispose()
{
// TODO: Add DisposeClass.Dispose implementation
Debug.WriteLine( "Dispose called!" );
GC.SuppressFinalize( this );
}
#endregion
}
通過如下的程式碼進行測試。
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!
顯然物件的解構函式沒有被呼叫。通過如上的實驗以及文字說明,大家會得到如下的一個對比表格。
|
解構函式
|
Dispose
方法
|
Close
方法
|
意義
|
銷燬物件
|
銷燬物件
|
關閉物件資源
|
呼叫方式
|
不能被顯示呼叫,會被
GC
呼叫
|
需要顯示呼叫
或者通過
using
語句
|
需要顯示呼叫
|
呼叫時機
|
不確定
|
確定,在顯示呼叫或者離開
using
程式塊
|
確定,在顯示呼叫時
|
那麼在定義一個型別的時候,是否一定要給出這三個函式地實現呢。
我的建議大致如下。
1.提供解構函式,避免資源未被釋放,主要是指非記憶體資源;
2.對於Dispose和Close方法來說,需要看所定義的型別所使用的資源(參看前面所說),而決定是否去定義這兩個函式;
3.在實現Dispose方法的時候,一定要加上“GC.SuppressFinalize( this )”語句,避免再讓GC呼叫物件的解構函式。
C#程式所使用的記憶體是受託管的,但不意味著濫用,好地程式設計習慣有利於提高程式碼的質量以及程式的執行效率。
1.提供解構函式,避免資源未被釋放,主要是指非記憶體資源;
2.對於Dispose和Close方法來說,需要看所定義的型別所使用的資源(參看前面所說),而決定是否去定義這兩個函式;
3.在實現Dispose方法的時候,一定要加上“GC.SuppressFinalize( this )”語句,避免再讓GC呼叫物件的解構函式。
C#程式所使用的記憶體是受託管的,但不意味著濫用,好地程式設計習慣有利於提高程式碼的質量以及程式的執行效率。
相關文章
- C# 託管資源和非託管資源C#
- 重學c#系列——c# 託管和非託管資源(三)C#
- 重學c#系列——c# 託管和非託管資源與程式碼相關(四)C#
- C# 託管堆 遭破壞 問題溯源分析C#
- C#如何載入嵌入到資源的非託管dllC#
- 淺談 js 中的 this 指向問題JS
- 記憶體洩漏問題分析之非託管資源洩漏記憶體
- 關於java執行緒釋放資源問題Java執行緒
- 請教:JBoss伺服器不能釋放資源的問題伺服器
- 淺談SQL Server中的快照問題SQLServer
- 託管與非託管的混合程式設計程式設計
- 【Golang】淺談協程併發競爭資源問題Golang
- jdbc statment 資源釋放問題(高手請進入)JDBC
- GOLANG中time.After釋放的問題Golang
- Oracle中的Session kill不釋放問題OracleSession
- C# Dispose()釋放順序雜談C#
- 先有雞or先有蛋?淺談資料拆分與特徵縮放的順序問題特徵
- 賜教:關於JBoss伺服器無法釋放資源的問題伺服器
- 開源託管站點大全
- 淺談Oracle中exists與in的執行效率問題Oracle
- Visual C#託管Socket的實現方法C#
- 從C++看C#託管記憶體與非託管記憶體C++C#記憶體
- C#中的委託C#
- MediaPlayer原始碼存在的記憶體洩漏問題,釋放資源的正確方式原始碼記憶體
- 淺談用Ollydbg跟蹤vb程式---soli 兄弟的問題
- 淺談前後端分離中的跨資源共享(CORS)後端CORS
- 淺談深度學習的落地問題深度學習
- 利用IDisposable介面構建包含非託管資源物件物件
- Azure AD(三)知識補充-Azure資源的託管標識
- Window黑客程式設計之資源釋放技術黑客程式設計
- 請教一個java程式記憶體釋放的問題Java記憶體
- C# Task若干問題淺析C#
- 淺談C#中的資料型別轉換與物件複製C#資料型別物件
- [.net 物件導向程式設計進階] (8) 託管與非託管物件程式設計
- 淺談C#中標準Dispose模式的實現C#模式
- 重學c#系列——非託管例項(五)C#
- 淺談遊戲的經濟資源系統遊戲
- 資料庫連線沒有釋放造成的奇怪問題資料庫