託管堆記憶體管理原則與記憶體回收
託管堆將對象依次儲存,記憶體回收之後進行一次記憶體整理,所以如果GC的頻繁發生會降低系統效能,因為GC對記憶體的清理造成對象在記憶體中的移動,在整個GC結束之前,CLR上的應用程式不可能繼續運行,.net 使用世代記憶體回收行程,CLR上的記憶體管理原則與OS的記憶體管理原則類似(CLR本身也就是虛擬機器)。
1.CLR記憶體管理
CLR的託管堆的將對象依次儲存在記憶體上,如果有新的記憶體請求,會在託管堆中遍曆,找到第一個大小符合要求的連續記憶體空間,將對象儲存在該空間。
,假設CLR從記憶體為4001的儲存塊(不知道CLR是不是使用分頁系統,所以只能用儲存塊來描述)開始定址,此時需要分配一個大小為2個單位的記憶體給新的對象,則CLR會由上向下遍曆,找到4004-4005連續空間可用,則將新的對象儲存在這塊連續空間上。
2.記憶體回收與記憶體整理
,GC運行時會掃描其託管堆,查看是否有沒有被引用的對象,如在GC運行時發現對象1的所用引用已經被移除,將在維護的記憶體表中對4001,4002,4003三個記憶體塊的標誌記為未佔用,然後GC會對託管記憶體進行整理
其實CLR的GC回收器是一個世代回收器即:CLR將其託管堆的記憶體分為多個世代,從第0代開始,新申請的對象都會被分配在第0代的記憶體空間上,如果第0代上發生過一次GC回收,或第0代的記憶體空間不夠(此時也將引發一次GC回收),CLR會在GC回收完成之後,將第0代上仍有的對象壓縮儲存並移至下一代(第1代),以此類推,原先第1代上已有的對象會被移至第2代....
這樣CLR實際上保證了第0代上的對象都是最新的對象,而世代越大的空間上儲存的對象就應該是越老的對象,一般最新的對象是最常修改的對象,所以在第0代上GC會執行的最有效率,記憶體的移動也會更快
非託管資源的回收
1.解構函式
在解構函式中標識該對象在釋放時應該釋放的非託管資源,代碼類似
class MyClass
{
~MyClass()
{
//釋放非託管資源,如資料庫連接,檔案,網路,流等
}
}
使用解構函式的缺點:
1.CLR不能保證GC會在何時執行,也就是說非託管資源不會在對象沒有用後立即釋放,而是需要等待GC的運行
2.GC在第一次調用解構函式時不會真正釋放非託管資源,而是在第二次調用時在真正釋放非託管資源
3.CLR使用一個獨立的線程來調用所有的解構函式,如果釋放資源過多會嚴重影響該線程的執行效率
2.IDisponse介面
IDisponse介面具有C#語言級的支援,,實現了IDispone介面的對象在使用時可以使用using來自動釋放其託管資源 (等價於try.finally語句塊),它的使用如下
class MyClass : IDisponse
{
public void Disponse()
{
//釋放非託管資源
}
}
class UseClass
{
using(MyClass myClass = new MyClass())
{
//使用MyClass
}
}
使用IDisponse的缺點
IDispone過於依賴人的調用,如果軟體開發過程中忘記了使用using或顯示調用,則非託管資源得不到有效釋放
3.綜合
綜合使用上面的兩種方法可以得到一個比較好的效果,代碼類似於
public MyClass : IDispose
{
private void isDisposed = false;//記錄是否已經對資源進行過釋放
//實現IDispone的方法
public void Dispose()
{
Dispose(true);
GC.SuppressFinallize(this);//在執行GC時當前類的解構函式不用再調用
}
protected virtual void Disponse(bool disposing)
{
if(!isDisposed)
{
if(disposing)
{
//使用Dispose時可以對一些託管資源進行釋放,
//如果是解構函式調用時由於GC已經執行,所以不能保證託管資源是有已經被釋放,所以最好不要對託管資源進行釋放
//調用託管資源的Dispose方法釋放其資源(託管&非託管)
}
//釋放非託管資源(斷開資料庫連接,關閉流等)
}
isDisponsed = true;
}
~MyClass
{
Dispose(false);
}
//一般該類的所有方法都應包含對isDispose的判斷
public void SomeMethod()
{
if(isDispose)
{
throw new ObjectDisposedException();
}
//方法的具體實現
}
}