託管堆:
1、垃圾回收器
.net的運行庫採用的是垃圾回收器來回收的方式。垃圾回收器本身也是一個程序。程序動態請求的內存都分配到堆上,在.net中,CLR維護它自己的託管堆供.net程序使用。
每隔一段時間.net就會檢查託管堆,當檢查到需要清理堆時,.net就調用垃圾回收器這個程序。垃圾回收器會掃描堆上的對象的引用,不再有引用的對象就被刪除。垃圾回收器調用的時間是不確定的,除非代碼中有調用垃圾回收器(System.GC.Collect()).
2、內存碎片的處理
由於堆的釋放時刻是由堆上對象的生存週期決定的,這就決定了堆的釋放順序是不定的,必然產生內存碎片。就像操作系統的磁盤分配機制一樣,若需要給一個新的對象分配空間,CLR需要搜索堆,直到找到足夠大的空間來存儲新對象。實際上垃圾回收器會避免堆出現碎片的現象。
回收器在一次釋放動作結束後會將堆上剩餘的對象往堆頂移動,回收器會更新被移動對象新的存儲地址。這就又形成了整塊的未分配的空間。
雖然移動對象並更新地址會消耗一定的性能,但是由於分配速度和訪問速度會快很多,足以彌補此消耗。
注:垃圾回收器並不保證每次回收動作都能將所有未被引用的對象刪除。
3、提高回收性能的機制
a、分代機制:所有新創建的對象會放在第0代堆上,直到回收器進行了一次清理動作後仍存在的對象會被移動到另一片連續的地址空間中,即第1代堆上。而之前第1代上剩餘的對象會被移動到第2代堆上,此時第0代堆空閒,等待新創建的對象。每一次回收動作都重複此過程。新的對象被回收的機率更大,將這些對象分階段存放,回收過程速度加快。
注:在創建新對象時,如果所需空間超出了第0代對應的空閒區域,回收器會進行垃圾回收,將第0代區域空閒出來。
b、大對象堆:當對象所需區域大於85000個字節時,對象會被放在大對象堆上。大對象堆是獨立於一般堆的存儲區域,大對象堆是不進行移動以達到獲得連續的空閒區域,因爲挪動大對象代價很大。
C、線程回收機制:第2代堆上的對象是生命週期較長的對象,第2代對象堆和大對象堆是通過線程來回收的。所以只有回收第0、1代堆對象時應該程序纔會阻塞執行。在大型的服務器程序中此功能優勢明顯。將配置文件元素<gcConcurrent>設爲false,此功能關閉
d、控制垃圾回收方式。GCSettings.LatencyMode屬性。此方式還有待理解
二、非託管堆
回收器不知道如何釋放非託管資源,如文件句柄,網絡連接、數據庫連接等。當非託管對象被託管對象引用時,託管對象被釋放時應確保其相關的非託管對象被釋放。
以下兩種方法可解決釋放非託管資源的問題:
1、在定義類時,聲明一個析構函數
在delphi中,我習慣在析構函數中做一些最後的清理工作,以確保對象釋放前將其類中用到的成員對象釋放。但是
.net因爲有垃圾回收器,且回收的時間是不定的,可能出現在調用析構函數的時候,對象已經被回收了。
所以只靠析構函數來達到此目的是不行的。通過析構函數和下面將要提到的IDisposable接口聯合適用是可以的。
2、在類中實現System.IDisposable接口
class MyClass:IDisposable
{
public void Dispose()
{
//釋放非託管資源
}
}
兩種調用方式:
a、 using關鍵字方式,對象聲明週期結束時會自動調用Dispose函數。此using與命名空間無關
using(MyClass classA= new MyClass())
{
//do something
}//對象的生命週期結束
b、主動調用
try
{
MyClass classA= new MyClass();
//do something
}
finally
{
Dispose();//顯示調用
}