有些對象需要顯示地銷毀代碼來釋放資源,比如打開的文件資源,鎖,操作系統(tǒng)句柄和非托管對象。在.NET中,這就是所謂的對象銷毀,它通過IDisposal接口來實現(xiàn)。不再使用的對象所占用的內存管理,必須在某個時候回收;這個被稱為無用單元收集的功能由CLR執(zhí)行。
對象銷毀和垃圾回收的區(qū)別在于:對象銷毀通常是明確的策動;而垃圾回收完全是自動地。換句話說,程序員負責釋放文件句柄,鎖,以及操作系統(tǒng)資源;而CLR負責釋放內存。
本章將討論對象銷毀和垃圾回收,還描述了C#處理銷毀的一個備選方案--Finalizer及其模式。最后,我們討論垃圾回收器和其他內存管理選項的復雜性。
| 對象銷毀 | 垃圾回收 |
| 1)IDisposal接口2) Finalizer | 垃圾回收 |
| 對象銷毀用于釋放非托管資源 | 垃圾回收用于自動釋放不再被引用的對象所占用的內存;并且垃圾回收什么時候執(zhí)行時不可預計的 |
| 為了彌補垃圾回收執(zhí)行時間的不確定性,可以在對象銷毀時釋放托管對象占用的內存 |
.NET Framework定義了一個特定的接口,類型可以使用該接口實現(xiàn)對象的銷毀。該接口的定義如下:
public interface IDisposable{void Dispose();}C#提供了鴘語法,可以便捷的調用實現(xiàn)了IDisposable的對象的Dispose方法。比如:
using (FileStream fs = new FileStream ("myFile.txt", FileMode.Open)){// ... Write to the file ...}編譯后的代碼與下面的代碼是一樣的:
FileStream fs = new FileStream ("myFile.txt", FileMode.Open);try{// ... Write to the file ...}finally{if (fs != null) ((IDisposable)fs).Dispose();}finally語句確保了Dispose方法的調用,及時發(fā)生了異常,或者代碼在try語句中提前返回。
在簡單的場景中,創(chuàng)建自定義的可銷毀的類型值需要實現(xiàn)IDisposable接口即可
sealed class Demo : IDisposable{public void Dispose(){// Perform cleanup / tear-down....}}請注意,對于sealed類,上述模式非常適合。在本章后面,我們會介紹另外一種銷毀對象的模式。對于非sealed類,我們強烈建議時候后面的那種銷毀對象模式,否則在非sealed類的子類中,也希望實現(xiàn)銷毀時,會發(fā)生非常詭異的問題。
Framework在銷毀對象的邏輯方面遵循一套規(guī)則,這些規(guī)則并不限用于.NET Framework或C#語言;這些規(guī)則的目的是定義一套便于使用的協(xié)議。這些協(xié)議如下:
這些規(guī)則同樣也適用于我們平常創(chuàng)建自定義類型,盡管它并不是強制性的。沒有誰能阻止你編寫一個不可銷毀的方法;然而,這么做,你的同事也許會用高射炮攻擊你。
對于第三條規(guī)則,一個容器對象自動銷毀其子對象。最好的一個例子就是,windows容器對象比如Form對著Panel。一個容器對象可能包含多個子控件,那你也不需要顯示地銷毀每個字對象:關閉或銷毀父容器會自動關閉其子對象。另外一個例子就是如果你在DeflateStream包裝了FileStream,那么銷毀DeflateStream時,F(xiàn)ileStream也會被銷毀--除非你在構造器中指定了其他的指令。
Close和Stop
有一些類型除了Dispose方法之外,還定義了Close方法。Framework對于Close方法并沒有保持完全一致性,但在幾乎所有情況下,它可以:
對于后者一個典型的例子就是IDbConnecton類型,一個Closed的連接可以再次被打開;而一個Disposed的連接對象則不能。另外一個例子就是Windows程序使用ShowDialog的激活某個窗口對象:Close方法隱藏該窗口;而Dispose釋放窗口所使用的資源。
有一些類定義Stop方法(比如Timer或HttpListener)。與Dipose方法一樣,Stop方法可能會釋放非托管資源;但是與Dispose方法不同的是,它允許重新啟動。
銷毀對象應該遵循的規(guī)則是“如有疑問,就銷毀”。一個可以被銷毀的對象--如果它可以說話--那么將會說這些內容:
“如果你結束對我的使用,那么請讓我知道。如果只是簡單地拋棄我,我可能會影響其他實例對象、應用程序域、計算機、網絡、或者數(shù)據(jù)庫”
如果對象包裝了非托管資源句柄,那么經常會要求銷毀,以釋放句柄。例子包括Windows Form控件、文件流或網絡流、網絡sockets,GDI+畫筆、GDI+刷子,和bitmaps。與之相反,如果一個類型是可銷毀的,那么它會經常(但不總是)直接或間接地引用非托管句柄。這是由于非托管句柄對操作系統(tǒng)資源,網絡連接,以及數(shù)據(jù)庫鎖之外的世界提供了一個網關(出入口),這就意味著使用這些對象時,如果不正確的銷毀,那么會對外面的世界代碼麻煩。
但是,遇到下面三種情形時,不要銷毀對象
第一種情況很少見。多數(shù)情形都可以在System.Drawing命名空間下找到:通過靜態(tài)成員或屬性獲取的GDI+對象(比如Brushed.Blue)就不能銷毀,這是因為該實現(xiàn)在程序的整個生命周期中都會用到。而通過構造器得到的對象實例,比如new SolidBrush,就應該銷毀,這同樣適用于通過靜態(tài)方法獲取的實例對象(比如Font.FromHdc)。
第二種情況就比較常見。下表以System.IO和System.Data命名空間下類型舉例說明
| 類型 | 銷毀功能 | 何時銷毀 |
| MemoryStream | 防止對I/O繼續(xù)操作 | 當你需要再次讀讀或寫流 |
| StreamReader,StreamWriter | 清空reader/writer,并關閉底層的流 | 當你希望底層流保持打開時(一旦完成,你必須改為調用StreamWriter的Flush方法) |
| IDbConnection | 釋放數(shù)據(jù)庫連接,并清空連接字符串 | 如果你需要重新打開數(shù)據(jù)庫連接,你需要調用Close方法而不是Dispose方法 |
| DataContext(LINQ to SQL) | 防止繼續(xù)使用 | 當你需要延遲評估連接到Context的查詢 |
第三者情況包含了System.ComponentModel命名空間下的這幾個類:WebClient, StringReader, StringWriter和BackgroundWorker。這些類型有一個共同點,它們之所以是可銷毀的是源于它們的基類,而不是真正的需要進行必要的清理。如果你需要在一個方法中使用這樣的類型,那么在using語句中實例化它們就可以了。但是,如果實例對象需要持續(xù)一段較長的時間,并記錄何時不再使用它們以銷毀它們,就會給程序帶來不惜要的復雜度。在這樣的情況下,那么你就應該忽略銷毀對象。
正因為IDisposable實現(xiàn)類可以使用using語句來實例化,因而這可能很容易導致該實現(xiàn)類的Dispose方法延伸至不必要的行為。比如:
public sealed class HouseManager : IDisposable{public void Dispose(){CheckTheMail();}...}想法是該類的使用者可以選擇避免不必要的清理--簡單地說就是不調用Dispose方法。但是,這就需要調用者知道HouseManager類Dispose方法的實現(xiàn)細節(jié)。及時是后續(xù)添加了必要的清理行為也破壞了規(guī)則。
public void Dispose(){CheckTheMail(); // NonessentialLockTheHouse(); // Essential}在這種情況下,就應該使用選擇性銷毀模式
public sealed class HouseManager : IDisposable{public readonly bool CheckMailOnDispose;public Demo (bool checkMailOnDispose){CheckMailOnDispose = checkMailOnDispose;}public void Dispose(){if (CheckMailOnDispose) CheckTheMail();LockTheHouse();}...}這樣,任何情況下,調用者都可以調用Dispose--上述實現(xiàn)不僅簡單,而且避免了特定的文檔或通過反射查看Dispose的細節(jié)。這種模式在.net中也有實現(xiàn)。System.IO.ComPRession空間下的DeflateStream類中,它的構造器如下
public DeflateStream (Stream stream, CompressionMode mode, bool leaveOpen)
非必要的行為就是在銷毀對象時關閉內在的流(第一個參數(shù))。有時候,你希望內部流保持打開的同時并銷毀DeflateStream以執(zhí)行必要的銷毀行為(清空bufferred數(shù)據(jù))
這種模式看起來簡單,然后直到Framework 4.5,它才從StreamReader和StreamWriter中脫離出來。結果卻是丑陋的:StreamWriter必須暴露另外一個方法(Flush)以執(zhí)行必要的清理,而不是調用Dispose方法(Framework 4.5在這兩個類上公開一個構造器,以允許你保持流處于打開狀態(tài))。System.Security.Cryptography命名空間下的CryptoStream類,也遭遇了同樣的問題,當需要保持內部流處于打開時你要調用FlushFinalBlock銷毀對象。
在一般情況下,你不要在對象的Dispose方法中清除該對象的字段。然而,銷毀對象時,應該取消該對象在生命周期內所有訂閱的事件。退訂這些事件避免了接收到非期望的通知--同時也避免了垃圾回收器繼續(xù)對該對象保持監(jiān)視。
設置一個字段用以指明對象是否銷毀,以便在使用者在該對象銷毀后訪問該對象拋出一個ObjectDisposedException,這是非常值得做的。一個好的模式就是使用一個public的制度的屬性:
public bool IsDisposed { get; private set; }盡管技術上沒有必要,但是在Dispose方法清除一個對象所擁有的事件句柄(把句柄設置為null)也是非常好的一種實踐。這消除了在銷毀對象期間這些事件被觸發(fā)的可能性。
偶爾,一個對象擁有高度秘密,比如加密密鑰。在這種情況下,那么在銷毀對象時清除這樣的字段就非常有意義(避免被非授權組件或惡意軟件發(fā)現(xiàn))。System.Security.Cryptography命令空間下的SymmetricAlgorithm類就屬于這種情況,因此在銷毀該對象時,調用Array.Clear方法以清除加密密鑰。
無論一個對象是否需要Dispose方法以實現(xiàn)銷毀對象的邏輯,在某個時刻,該對象在堆上所占用的內存空間必須釋放。這一切都是由CLR通過GC自動處理. 你不需要自己釋放托管內存。我們首先來看下面的代碼
public void Test(){byte[] myArray = new byte[1000];}當Test方法執(zhí)行時,在內存的堆上分配1000字節(jié)的一個數(shù)組;該數(shù)組被變量myArray引用,這個變量存儲在變量棧上。當方法退出后,局部變量myArray就失去了存在的范疇,這也意味著沒有引用指向內存堆上的數(shù)組。那么該孤立的數(shù)組,就非常適合通過垃圾回收機制進行回收。
垃圾回收機制并不會在一個對象變成孤立的對象之后就立即執(zhí)行。與大街上的垃圾收集不一樣,.net垃圾回收是定期執(zhí)行,盡享不是按照一個估計的計劃。CLR決定何時進行垃圾回收,它取決于許多因素,比如,剩余內存,已經分配的內存,上一次垃圾回收的時間。這就意味著,在一個對象被孤立后到期占用的內存被釋放之間,有一個不確定的時間延遲。該延遲的范圍可以從幾納秒到數(shù)天。
垃圾回收和內存占用垃圾收集試圖在執(zhí)行垃圾回收的時間與程序的內存占用之間建立一個平衡。因此,程序可以占用比它們實際需要更多的內存,尤其特現(xiàn)在程序創(chuàng)建的大的臨時數(shù)組。你可以通過Windows任務管理器監(jiān)視某一個進程內存的占用,或者通過編程的方式查詢性能計數(shù)器來監(jiān)視內存占用:// These types are in System.Diagnostics:string procName = Process.GetCurrentProcess().ProcessName;using (PerformanceCounter pc = new PerformanceCounter("Process", "Private Bytes", procName))Console.WriteLine (pc.NextValue());上面的代碼查詢內部工作組,返回你當前程序的內存占用。尤其是,該結果包含了CLR內部釋放,以及把這些資源讓給操作系統(tǒng)以供其他的進程使用。 |
根就是指保持對象依然處于活著的事物。如果一個對象不再直接或間接地被一個根引用,那么該對象就適合于垃圾回收。
一個跟可以是:
正在執(zhí)行的代碼可能涉及到一個已經刪除的對象,因此,如果一個實例方法正在執(zhí)行,那么該實例方法的對象必然按照上述方式被引用。
請注意,一組相互引用的對象的循環(huán)被視作無根的引用。換一種方式,也就是說,對象不能通過下面的箭頭指向(引用)而從根獲取,這也就是引用無效,因此這些對象也將被垃圾回收器處理。
新聞熱點
疑難解答