在NET的所有技術中最具爭議的恐怕是垃圾收集(Garbage CollectionGC)了作為NET框架中一個重要的部分托管堆和垃圾收集機制對我們中的大部分人來說是陌生的概念在這篇文章中將要討論托管堆和你將從中得到怎樣的好處
為什麼要托管堆?
NET框架包含一個托管堆所有的NET語言在分配引用類型對象時都要使用它像值類型這樣的輕量級對象始終分配在棧中但是所有的類實例和數組都被生成在一個內存池中這個內存池就是托管堆
垃圾收集器的基本算法很簡單
● 將所有的托管內存標記為垃圾
● 尋找正被使用的內存塊並將他們標記為有效
● 釋放所有沒有被使用的內存塊
● 整理堆以減少碎片
托管堆優化
看上去似乎很簡單但是垃圾收集器實際采用的步驟和堆管理系統的其他部分並非微不足道其中常常涉及為提高性能而作的優化設計舉例來說垃圾收集遍歷整個內存池具有很高的開銷然而研究表明大部分在托管堆上分配的對象只有很短的生存期因此堆被分成三個段稱作generations新分配的對象被放在generation 中這個generation是最先被回收的——在這個generation中最有可能找到不再使用的內存由於它的尺寸很小(小到足以放進處理器的L cache中)因此在它裡面的回收將是最快和最高效的
托管堆的另外一種優化操作與locality of reference規則有關該規則表明一起分配的對象經常被一起使用如果對象們在堆中位置很緊湊的話高速緩存的性能將會得到提高由於托管堆的天性對象們總是被分配在連續的地址上托管堆總是保持緊湊結果使得對象們始終彼此靠近永遠不會分得很遠這一點與標准堆提供的非托管代碼形成了鮮明的對比在標准堆中堆很容易變成碎片而且一起分配的對象經常分得很遠
還有一種優化是與大對象有關的通常大對象具有很長的生存期當一個大對象在NET托管堆中產生時它被分配在堆的一個特殊部分中這部分堆永遠不會被整理因為移動大對象所帶來的開銷超過了整理這部分堆所能提高的性能
關於外部資源(External Resources)的問題
垃圾收集器能夠有效地管理從托管堆中釋放的資源但是資源回收操作只有在內存緊張而觸發一個回收動作時才執行那麼類是怎樣來管理像數據庫連接或者窗口句柄這樣有限的資源的呢?等待直到垃圾回收被觸發之後再清理數據庫連接或者文件句柄並不是一個好方法這會嚴重降低系統的性能
所有擁有外部資源的類在這些資源已經不再用到的時候都應當執行Close或者Dispose方法從Beta(譯注本文中所有的Beta均是指NET Framework Beta不再特別注明)開始Dispose模式通過IDisposable接口來實現這將在本文的後續部分討論
需要清理外部資源的類還應當實現一個終止操作(finalizer)在C#中創建終止操作的首選方式是在析構函數中實現而在Framework層終止操作的實現則是通過重載SystemObjectFinalize 方法以下兩種實現終止操作的方法是等效的
~OverdueBookLocator()
{
Dispose(false);
}
和
public void Finalize()
{
baseFinalize();
Dispose(false);
}
在C#中同時在Finalize方法和析構函數實現終止操作將會導致錯誤的產生
除非你有足夠的理由否則你不應該創建析構函數或者Finalize方法終止操作會降低系統的性能並且增加執行期的內存開銷同時由於終止操作被執行的方式你並不能保證何時一個終止操作會被執行
內存分配和垃圾回收的細節
對GC有了一個總體印象之後讓我們來討論關於托管堆中的分配與回收工作的細節托管堆看起來與我們已經熟悉的C++編程中的傳統的堆一點都不像在傳統的堆中數據結構習慣於使用大塊的空閒內存在其中查找特定大小的內存塊是一件很耗時的工作尤其是當內存中充滿碎片的時候與此不同在托管堆中內存被組制成連續的數組指針總是巡著已經被使用的內存和未被使用的內存之間的邊界移動當內存被分配的時候指針只是簡單地遞增——由此而來的一個好處是分配操作的效率得到了很大的提升
當對象被分配的時候它們一開始被放在generation 中當generation 的大小快要達到它的上限的時候一個只在generation 中執行的回收操作被觸發由於generation 的大小很小因此這將是一個非常快的GC過程這個GC過程的結果是將generation 徹底的刷新了一遍不再使用的對象被釋放確實正被使用的對象被整理並移入generation 中
當generation 的大小隨著從generation 中移入的對象數量的增加而接近它的上限的時候一個回收動作被觸發來在generation 和generation 中執行GC過程如同在generation 中一樣不再使用的對象被釋放正在被使用的對象被整理並移入下一個generation中大部分GC過程的主要目標是generation 因為在generation 中最有可能存在大量的已不再使用的臨時對象對generation 的回收過程具有很高的開銷並且此過程只有在generation 和generation 的GC過程不能釋放足夠的內存時才會被觸發如果對generation 的GC過程仍然不能釋放足夠的內存那麼系統就會拋出OutOfMemoryException異常
帶有終止操作的對象的垃圾收集過程要稍微復雜一些當一個帶有終止操作的對象被標記為垃圾時它並不會被立即釋放相反它會被放置在一個終止隊列(finalization queue)中此隊列為這個對象建立一個引用來避免這個對象被回收後台線程為隊列中的每個對象執行它們各自的終止操作並且將已經執行過終止操作的對象從終止隊列中刪除只有那些已經執行過終止操作的對象才會在下一次垃圾回收過程中被從內存中刪除這樣做的一個後果是等待被終止的對象有可能在它被清除之前被移入更高一級的generation中從而增加它被清除的延遲時間
需要執行終止操作的對象應當實現IDisposable接口以便客戶程序通過此接口快速執行終止動作IDisposable接口包含一個方法——Dispose這個被Beta引入的接口采用一種在Beta之前就已經被廣泛使用的模式實現從本質上講一個需要終止操作的對象暴露出Dispose方法這個方法被用來釋放外部資源並抑制終止操作就象下面這個程序片斷所演示的那樣
public class OverdueBookLocator: IDisposable
{
~OverdueBookLocator()
{
InternalDispose(false);
}
public void Dispose()
{
InternalDispose(true);
}
protected void InternalDispose(bool disposing)
{
if(disposing)
{
GCSuppressFinalize(this);
// Dispose of managed objects if disposing
}
// free external resources here
}
}
From:http://tw.wingwit.com/Article/program/net/201311/12014.html