.NET 分代垃圾回收
.NET框架包含一個託管堆,所有的.NET語言在分配引用型別物件時都要使用它。像值型別這樣的輕量級物件始終分配在棧中,但是所有的類例項和陣列都被生成在一個記憶體池中,這個記憶體池就是託管堆。
.NET框架中的垃圾回收器被稱為分代的垃圾回收器(Generational Garbage Collector),也就是說被分配的物件劃分為3個類別,或稱為“代”。分別為0,1,2。0、1、2代對應的託管堆的初始化大小分別是256K,2M和10M。垃圾回收器在發現改變大小能夠提高效能的話,會改變託管堆的大小。例如當應用程式初始化了許多小的物件,並且這些物件會被很快回收的話,垃圾回收器就會將第0代的託管堆變為128K,並且提高回收的頻率。如果情況相反,垃圾回收器發現在第0代的託管堆中不能回收很多空間時,就會增加託管堆的大小。在應用程式初始化的之前,所有等級的託管堆都是空的。當物件被初始化的時候,他們會按照初始化的先後順序被放入第0代的託管堆中。
最近被分配記憶體空間的物件被放置於第0代,因為第0代很小,小到足以放進處理器的二級(L2)快取,所以第0代能夠為我們提供對其中物件的快速存取。經過一輪垃圾回收後,仍然保留在第0代中的物件被移進第1代中,再經過一輪垃圾記憶體回收後,仍然保留在第1代中的物件則被移進第2代中。第2代包含了生存期較長的物件,這些物件至少經過了兩輪迴收。
C#程式為一個物件分配記憶體時,託管堆幾乎可以立即返回新物件所需的記憶體,託管堆之所以能有這樣高效的記憶體分配效能是由於託管堆較為簡單的資料結構。託管堆類似於簡單的位元組陣列,有一個指向第一個可用記憶體空間的指標。
在某塊被某物件所請求時,上述指標值就會返回給呼叫函式,而指標會重新調整至指向下一個可用的記憶體空間。分配一個託管記憶體塊只比遞增一個指標的值稍微複雜一點。這也是託管堆所優化的效能之一。在一個不需太多垃圾回收的應用程式中,託管堆的表現會優於傳統的堆。
由於這個線性的記憶體分配方法的存在,在C#應用程式中同時分配的物件在託管堆上通常會被分配成彼此相鄰。著安排和傳統的堆記憶體分配完全不同,傳統的堆記憶體分配是基於記憶體塊大小的。
例如,兩個同時分配的物件在堆上的位置可能相距很遠,從而降低了快取的效能。因此雖然記憶體分配很快,但在一些比較重要的程式中,第0代中的可用記憶體很有可能會徹底被消耗光。
記住,第0代小到可以裝進L2緩衝區,並且沒有被使用的記憶體不會被自動釋放。當第0代中沒有可以分配的有效記憶體時,就會在第0代中觸發一輪垃圾回收,在這輪垃圾回收中將刪除所有不再被引用的物件,並將當前正在使用中的物件移至第1代。
針對第0代的垃圾回收是最常見的回收型別,而且速度很快。在第0代的垃圾記憶體回收不能有效的請求到充足的記憶體時,就啟動第1代的垃圾記憶體回收。第2代的垃圾記憶體回收要作為最後一種手段而使用,當且僅當第1代和第0代的垃圾記憶體回收不能被提供足夠記憶體時進行。如果各代都進行了垃圾回收後仍沒有可用的記憶體,就會引發一個OutOfMemeryException異常 。