【转】.net框架读书笔记---CLR内存管理\垃圾收集(二)
前幾天學習了CLR垃圾收集原理和基本算法,但是那些是僅僅相對于托管堆而言的,任何非托管資源的類型,例如文件、網絡資源等,都必須支持一種稱為終止化(finalization)的操作。
終止化
終止化操作允許一種資源在他所占的內存被回收之前首先執行一些清理工作。要提供終止化操作操作,必須為類型實現一個名為Finalize的方法。當垃圾收集器判定一個對象為可收集的垃圾時,它便會調用該對象的Finalize方法(如果存在的話)。
C#為定義Finalize方法提供了特殊的語法看下面代碼;
public?class?OSHandler
{
private?IntPtr handler;
public?OSHandler(IntPtr handler)
{
handler?=?handler;
}
//?當垃圾收集器執行時,該析構函數將被調用,它將關閉非托管資源句柄
~?OSHandler()
{
CloseHandler(handler);
}
public?IntPtr ToHandler()
{
return?handler;
}
//?釋放非托管資源
[System.Runtime.InteropServices.DllImport(?"?Kernel32?"?)]
private?extern?static?bool?CloseHandler(IntPtr handler);
}
查看中間語言
.method family hidebysig?virtual?instance?void
Finalize() cil managed
{
//?代碼大小 26 (0x1a)
.maxstack?1
.?try
{
IL_0000: nop
IL_0001: ldarg.?0
IL_0002: ldfld native?int?FinalizeStudy.OSHandler::?'?handler?'
IL_0007: call?bool?FinalizeStudy.OSHandler::CloseHandler(native?int?)
IL_000c: pop
IL_000d: nop
IL_000e: leave.s IL_0018
}?//?end .try
finally
{
IL_0010: ldarg.?0
IL_0011: call instance?void?[mscorlib]System.Object::Finalize()
IL_0016: nop
IL_0017: endfinally
}?//?end handler
IL_0018: nop
IL_0019: ret
}?//?end of method OSHandler::Finalize
?
會發現,析構函數被編譯器編譯為Finalize函數,并且使用了異常處理。
這樣當未來某個時刻垃圾收集器判定對象為可收集的垃圾時,它會看到該類型定義有一個Finalize方法,于是它便會調用該方法,從而允許CLoseHandler函數來關閉其中的非托管資源。在Finalize方法返回之后的某個時刻,該OSHandler對象在托管堆中所占的內存才會被回收。
應該避免使用Finalize方法。有以下原因:
- 實現了Finalize的對象其代齡會被提高,增加內存的壓力,甚至被該對象直接或者間接引用的對象的代齡也將被提升(以后學習代齡)。
- 終止化對象的分配花費的時間較長,因為指向它們的指針必須被放在終止化鏈表上;
- 強制垃圾收集器執行Finalize方法會極大的損失程序的性能;
- 不能控制Finalize方法何時執行。對象可能會一直占有著資源,直到出現垃圾收集;
- CLR不對Finalize方法的執行順序做任何的保障。加入對象包含指向另一個對象的指針,兩個對象都可能會被垃圾收集,順序的不一樣會導致結果不可預期。靠,個人感覺這就是一個bug。
終止化操作的內部機理
創建一個新對象,new先為對象在托管堆上面分配內存。如果對象的類型定義了FInalize方法,那么在該類型的實例被調用之前,指向該對象的一個指針將被放到一個稱為終止化鏈表(finalization list)的數據結構里面。終止化鏈表是一個由垃圾收集器控制的內部數據結構。鏈表上的每一個條目都引用著一個對象。這實際告訴垃圾收集器在回收這些對象的內存之前要首先調用它們的Finalize方法。
當垃圾收集檢測到可收集的垃圾時,垃圾收集器會掃描終止化鏈表是否有執行可收集垃圾的對象,當找到這樣的指針,它們會從終止化鏈表移除,并添加到一個稱為終止化可達列表(freachable queue)的數據結構上。在終止化可達列表上出現的對象表示該對象的Finalize方法即將被調用,當垃圾收集完畢后,沒有Finalize的對象的內存將被回收,實現了Finalize的對象內存卻不能被回收,因為他們的Finalize方法還沒有被調用。CLR有一個特殊的高優先級的線程用來專門調用Finalize方法。該線程可以避免線程同步問題。
非常有意義的是,當垃圾收集器將一個對象從終止化鏈表轉移到終止化可達隊列時,該對象不再認為是可收集的垃圾對象,它的內存也就不可能被回收。到此為止,垃圾收集器完成了垃圾對象的鑒別工作,一些原先認為是垃圾的對象現在被認為不是垃圾,從某種意義上來說,對象又“復蘇”了。當第一次垃圾收集執行完畢后,特殊的CLR線程將會清空終止化可達隊列中的對象,同時執行其中某個對象的Finalize方法。
等下一次垃圾收集執行的時候,它會看到這些終止化對象已經成為真正的垃圾對象,這樣實現了Finalize的對象的內存才被完全回收。 實際上終止化對象需要執行兩次垃圾收集才能釋放它所占用的內存。實際上由于代齡的提高,可能收集次數會多于兩次。上面這些玩意在Effective C#里面也講過,以前沒有看懂。
?
Dispose模式
感覺CLR的終止化是個吃力不討好的玩意
- 分配起來慢(加入終止化鏈表),
- 收集起來更慢,先是加入可達終止化列表,讓對象復活,二次垃圾回收才能收集;
- 不能人為的控制,長時間占用內存;
- 增加對象的代齡,更是不可饒恕。
- 怎么辦??
微軟總是NB的,作者總是掉人胃口的,CLR提供了顯式釋放或者關閉對象的能力,但是類型需要實現一種被稱為Dispose的模式(當然有一些約定)。如果一個類型實現了Dispose模式,使用該類型的開發人員將能夠知道當對象不再被使用時如何顯式地釋放掉它所占用的資源。
新版本的OSHandler實現,應用了Dispose接口:
public?class?OSHandler:IDisposable
{
private?IntPtr handler;
public?OSHandler(IntPtr handler)
{
this?.handler?=?handler;
}
//?當垃圾收集器執行時,該析構函數將被調用,它將關閉非托管資源句柄
~?OSHandler()
{
Dispose(?false?);
}
public?IntPtr ToHandler()
{
return?handler;
}
//?釋放非托管資源
[System.Runtime.InteropServices.DllImport(?"?Kernel32?"?)]
private?extern?static?bool?CloseHandler(IntPtr handler);
public?void?Dispose()
{
//?因為對象的資源被顯示清理,所以在這里阻止垃圾收集器調用Finalize方法
GC.SuppressFinalize(?this?);
//?進行實際清理工作
Dispose(?true?);
}
//?可以替換Dispose方法
public?void?Close()
{
Dispose();
}
//?執行清理工作,protected為了子類
protected?void?Dispose(?bool?disposing)
{
//?線程安全
lock?(?this?)
{
if?(disposing)
{
//?對象正在被被顯式關閉,此時可以引用其他對象,因為Finalize方法還沒有被執行
}
}
if?(IsValid)
{
//?如果handler有效,那么關閉之
CloseHandler(handler);
handler?=?InvalidHandler;?//?置為無效,防止多次調用
}
}
//?返回一個無效的句柄值
public?IntPtr InvalidHandler{?get?{?return?IntPtr.Zero;}}
//?判斷句柄是否有效
public?bool?IsValid {?get?{?return?handler?!=?InvalidHandler; } }
}
調用上面的Dispose或者Close方法只是顯式釋放非托管資源,并不會釋放托管堆中占用的內存,釋放對象內存的工作仍然由垃圾收集器負責,當然釋放時間仍然是不確定的。
上面的代碼中Finalize中Dispose方法的disposing參數被設為fasle。這將告訴Dispose方法不應該執行任何其他對象的代碼。在Close和無參Dispose方法中disposing參數為true,因為是手動執行,程序邏輯可以控制,可以在if中執行代碼。調用SuppressFinalize主要是為了避免終止化對象給垃圾收集器帶來負擔。
既然已經有了手動關閉的方法,為什么還要實現Finalize方法呢,因為我們不能保證程序的使用者一定會調用Dispose方法或者Close方法,如果不調用將會造成資源浪費,甚至系統崩潰,但是這不是使用者的錯誤,我們的程序應該考慮到這一點,實現Finalize就是為了防止這種情況出現,作為一個后備吧。
總結
以上是生活随笔為你收集整理的【转】.net框架读书笔记---CLR内存管理\垃圾收集(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 春节期间怎么使用信用卡最划算
- 下一篇: 【转】ABP源码分析四十二:ZERO的身