C# garbage collection 筆記 |
2007/06/06 ~ 阿亮 ~ |
最近有點被 C# 的 Garbage collection(GC) 的機制搞得有點昏頭 !!昏頭!! 故查點資料整理一下。
主要參考自 .NET Gotchas 這本書的 Garbage Collection Gotchas 部份:
一、之前在 class 內的物件,一般在 destruct 過程內處理,比如
class SomeClass { private OtherClass ref1; public SomeClass(OtherClass givenObject) { ref1 = givenObject; } ~SomeClass() { ref1 = null; // no meaning. } }
在 C# 內,也是有 ~SomeClass 這種 destruct 的寫法,但意義己不同於 C++ 的 destruct,在 C# 內稱之為 Finalize() 的做法,而 destruct 是種 pseudo-destruct,在 compile 後,會將此 pseudo-destruct 編成 Finalize();而這個 Finalize() 並不會在於使用者將 class SomeClass 宣告成 null 後馬上被進行(C++ 的做法),而是在 GC 在一段時間後決定回收此 class 的資源時,才會去進行 Finalize()。
回到上述的問題,可能在 SomeClass 被處理後,隔好久一段時間後 GC 才處理 ~SomeClass(),此時 ref1 的實際 giveObject 物件可能在別處早就被「處理」成 null 了;另外,在 SomeClass 被處理後,GC 會去除此物件的標記,而變為 inaccessible,當然在內的 ref1 也會變成 inaccessible,所以,做 ref1 = null; 這個動作是沒有意義的。呵~若熟 C++ 的人,一定覺得這個蠻怪的~
所以,不要將 C++ destruct 的觀念用在這裡,用起來一定會覺得很怪,特別在 Trace 每個 class 使用記憶體的情形下 :)
在 Finalize() 內不用特別針對 class 內物件設定 ref1 = null 的情形,同理,由於各 class 進行 GC 的順序也不一定,所以,也不要在 Finalize() 內對其他物件做 method 的動作,比如 ref1.close(),在某些情形下,會造成 deadlock.
Finalize() 屬 protected overridable,所以,在內不應呼叫 base.Finalize().
二、GC 的機制讓使用者不用管 managed resource 的管理(即 .NET 的部份),但 unmanaged resource 的部份就必需馬上處理了,比如 .NET 物件有去調用 COM 元件,而 COM 元件即屬於 unmanaged resource,GC 並不會幫忙處理此類的資源。所以,在 .NET 內提供 IDisposable 的介面,讓 .NET 元件內,提供 Dispose() 函數來處理 unmanaged resource 的釋放,當您想讓你的程式碼決定什麼時候釋放,可以用之。
//Wrapper.cs using System; using ACOMCompLib; using System.Runtime.InteropServices.Marshal; public class Wrapper : IDisposable { IMyComp comp = new MyCompClass(); public int doSomething() { int result; comp.doSomething(out result); return result; } ~Wrapper() { ReleaseComObject(comp); } #region IDisposable Members public void Dispose() { ReleaseComObject(comp); } }
其中,ReleaseComObject 屬於 System.Runtime.InteropServices.Marshal 下的 Method,用來釋放 COM 元件的資源,由於相對於 .NET 元件,可能 COM 元件佔的資源會很大,不能等到 GC 進行 Finalize() 時才釋放該 COM 元件的資源,所以, .NET 提供 Dispose() 讓使用者在於迴圈多次使用時,可以決定什麼時候釋放。
for(int i = 0; i < iterations; i++) { Wrapper theWrapper = null; try { theWrapper = new Wrapper(); result = theWrapper.doSomething(); } finally { theWrapper.Dispose(); } }
回到前面所提到的 Finalize(),其實做的事情就和 Dispose() 一樣,都在處理 unmanaged resource 為主,但 Finalize() 則交給 GC 安排時間去處理, Dispose() 則讓使用者使用的時機。
可在 Dispose() 結束前,呼叫 GC.SuppressFinalize(); 則 GC 則不會再處理此 class 的 Finalize() 了。
bool disposing=true/false 都表示有做 resource cleanup 的動作,唯 disposing=true 表示做了 IDisposable.Dispose(),而 disposing=false 表示做了 Finalize()。
三、可讓 Dispose() 處理 managed and unmanaged resource 管理,而 Finalize() 處理 unmanaged resource,所以,有類似下述的設計
protected virtual void Dispose(bool disposing) { if (!disposed) { if (disposing) { // Code to clean up managed resources } // Code to clean up unmanaged resources } disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~Base() { Dispose(false); } }
2008.03 補充
- 由於 Finalizer 的執行的時間和順序,「很自然地」屬於不可預期,而且有可能某個 instance 都不會被呼叫到 所以,對於 unmanaged resource 的釋放不要依賴 GC 來處理,特別是 file handle 或者 database connection.
- 不要讓不同 Thread 同時使用 Dispose(),會有不可預期的情形。
- 所以,除非有用到 unmanaged resource 並需要釋放,才用 Dispose/Finaliz。
其他 References:
唉~ 雖然整理出來,好像不是我目前所遇到的問題? Orz
ExecuteSelectCommand 我沒用過,略查一下是 COM 元件?而且我猜和 SQL 有關的東西,那可能要檢查一下是否 connection 沒有 close 掉? ?_?
請教一下,因為我的 Web 常出現 Outofmemory….
是否因為程式有太多 Session,或 DataSet、DataTable 之類的沒有釋掉掉的原因?
在 C#程式中,利用 Dispose() 是否真的釋放掉資源?或者有什麼好的寫法?
Ex: DataTable lDT=this.ExecuteSelectCommand(pSQL).Tables[0];
在資料取得之後寫 lDT.Dispose();