標籤:des blog class int art http
記憶體回收
1. .Net記憶體回收中涉及的名稱1.1.什麼是代?
記憶體回收行程為了提升效能使用了代的機制,共分為三代(Gen0、Gen1、Gen2)。GC工作機制基於以下假設,
1) 對象越新,生存期越短
2) 對象越老,生存期越長
3) 回收堆的一部分比回收整個堆時間短
在應用程式的生命週期中,最近建立的對象被分配在第0代,在一次記憶體回收之後存活下來的進入下一代。這樣可以使GC專註於回收最有可能存在更多可回收對象的第0代(最近分配的最有可能很快被釋放)
1.2 什麼時候發生記憶體回收?
1) 第0代滿工作原理
2) 代碼顯示調用GC.Collect方法
3) Windows報告記憶體不足
CLR註冊了Win32,CreateMemoryResourceNotification和QueryMemoryResourceNotification監視系統總體記憶體使用量情況,如果收到window報告記憶體不足的通知,強行執行GC
4) CLR卸載AppDomain
5) CLR關閉
1.3什麼是大對象堆?
採用大對象堆是記憶體回收另外一個效能提升的策略,任何大於等於85000bytes的對象都被視為大對象在特殊的大對象堆中分配。
大對象堆的回收策略:
(1) 大對象堆被認為是第2代的一部分,大對象堆回收時候同時回收第2代
(2) 大對象堆不進行壓縮操作(因為太耗時耗力)
根據該策略我們可以推測如果大對象頻繁的被分配將造成頻繁的第2代記憶體回收(即完全記憶體回收),對效能造成較大影響。
1.4什麼是root?
靜態對象
方法參數
局部變數
CPU寄存器
1.5什麼是finalizer?
大多數時候我們建立的類不包含非託管資源,因此只需要直接使用,CLR自然會判斷其生命週期結束而後回收相應的託管資源。但如果我們建立了含有非託管資源的類,CLR提供了finalizer機制來協助自動釋放非託管資源。
實現finalizer的文法和解構函式相似,實現了這個類似於解構函式的方式實際上被隱式轉換成了重載父類Finalize方法(object類預設提供了finalize方法)。
Class car
{
~car()//destructor
{
//cleanup statements
}
}
轉換後
Protected override void Finalize()
{
Try
{
//cleanup statements….
}
Finally
{
Base.Finalize();
}
}
1.6什麼是finalizequeue?
在建立一個類的執行個體時,如果該類定義了Finalize方法,那麼該類在構造器調用之前會將指向該對象的指標存放在一個叫finalization list中。記憶體回收時如果該對象被認定為垃圾,那麼CLR會從finalizationlist中尋找是否存在相應的對象指標,如果存在則將該指標移除,然後在freachable隊列中加入該對象指標,CLR提供了一個高優先順序的Finalizer線程來專門負責調用freachable隊列中對象的finalize方法以釋放資源。
1.7什麼情況下會發生Out of memory exception?
在一個記憶體配置請求到達時,CLR發現第0代沒有足夠空間從而觸發第0代GC,如果還是沒有足夠的記憶體,CLR發起完全GC,接下來CLR嘗試增大第0代的大小,如果沒有足夠的地址空間來增大第0代大小或滿足記憶體配置請求,就會拋出OutOfMemoryException。
因此發生OutOfMemoryException的兩個可能性是:
(1) 虛擬位址空間耗盡
(2) 實體記憶體耗盡
1.8什麼情況下要實現IDisposible介面?
IDisposible最重要的目的是釋放非託管資源,記憶體回收可以自動回收託管資源,但是對於程式中使用的非託管資源卻一無所知,例如資料庫連接、物件控點等
MSDN中給了正確的IDisposable介面的正確實現,這個實現中最容易被誤解的是protected virtual void Dispose(bool disposing)方法中布爾參數disposing的作用是什麼。
參數disposing的目的是在顯示調用Dispose方法或隱式調用Finalizer的情況下區別對待託管資源,在兩種情況下對於非託管資源的處理是一致的,直接釋放,不應該將非託管資源的釋放放在if(disposing)的處理中。
為什麼要區別對待託管資源?在顯示調用dispose方法的時候可以保證其內部引用了託管資源未被回收,所有可以直接調用其相應的釋放方法。但是finalizer被調用dispose的方法時,由於GC無法保證託管資源的釋放順序,在dispose方法中不應該再去訪問內部的託管資源,有可能內部的託管資源已經被釋放掉了。
1.9什麼情況下用GC.Collect?
大多數情況下我們都應該避免調用GC.Collect方法,讓記憶體回收行程自動執行,但是還是有些情況比如在某個時刻會發生一次非重複性事件導致大量的對象死亡,這個時候我們可以不依賴於記憶體回收行程的自動機制,手動調用GC.Collect方法。記住不要為了改善應用程式相應時間而調用GC.Collect,而是應該處於減少工作集的目的。
通過編程使用GC.Collect()強制進行可能會有好處。說得更明確就是:
(1) 應用程式將要進入一段代碼,後者不希望被可能的記憶體回收中斷。
(2) 應用程式剛剛分配非常多的對象,你想儘可能多地刪除已獲得的記憶體。
2. 託管堆最佳化
.Net架構套件含一個託管堆,所有的.Net語言在分配參考型別對象時都要使用它。像實值型別這樣的輕量級對象始終分配在棧中,但是所有的類執行個體和數組都被產生在一個記憶體池中,這個記憶體池就是託管堆
垃圾收集器的基本演算法很簡單:
(1) 將所有的託管記憶體標記為垃圾
(2) 尋找正被使用的記憶體塊,並將他們標記為有效
(3) 釋放所有沒有被使用的記憶體塊
(4) 整理堆以減少片段
垃圾收集器遍曆整個記憶體池開銷很高,然而,大部分在託管堆上分配的對象只有很短的生存期,因此堆被分成3個段,新分配的對象被放在generation()中,這個generation是最先被回收的—在這個generation中最有可能找到不再使用的記憶體,由於它的尺寸很小(小到足以放進處理器的L2cache中),因此在它裡面的回收將是最快和最有效。
託管堆的另外一種最佳化操作與locality ofreference規則有關,該規則表明,一起分配的對象經常被一起使用。如果對象們在堆中位置很緊湊的話,快取的效能將會得到提高。由於託管堆的的天性,對象們總是被分配在連續的地址上,託管堆總是保持緊湊,結果使得對象們失蹤彼此靠近,永遠不會分的很遠。這一點與標準堆提供的Unmanaged 程式碼形成了鮮明的對比,在標準堆中,堆很容易變成片段,而且一起分配的對象經常分的很遠。
還有一種最佳化是與大對象有關的。通常,大對象具有很長的生存期,當一個大對象在.net託管堆中產生時,它被分配在堆的一個特殊部分中,這部分堆永遠不會被整理。因為移動大對象所帶來的開銷超過了整理這部分堆所能提高的效能。
3. 外部資源
垃圾收集器能夠有效地管理從託管堆中釋放的資源,但是資源回收操作只有在記憶體緊張而觸發一個回收動作時才執行。那麼。類是怎樣來管理像資料庫連接或者視窗控制代碼這樣有限的資源的呢?
所有擁有外部資源的類,在這些資源已經不再用到的時候,都應當執行close或者Dispose方法,從.Net FrameworkBeta2開始,Dispose模式通過IDisposable介面來實現。
需要清理外部資源的類還應當實現一個終止操作(finalizer)。在C#中,建立終止操作的首選方式是在解構函式中實現,而在Framework層,終止操作的實現則是通過重載System.Object.Finalize方法。以下兩種實現終止操作的方法是等效的:
~OverdueBookLocator()
{
Dispose(false);
}
和:
public void Finalize()
{
base.Finalize();
Dispose(false);
}
在C#中,同時在Finalize方法和解構函式實現終止操作將會導致錯誤的產生。
除非你有足夠的理由,否則你不應該建立解構函式或者Finalize方法。終止操作會降低系統的效能,並且增加執行期的記憶體開銷。同時,由於終止操作被執行的方式,你並不能保證何時一個終止操作會被執行。
4. Finalize()-終結和Dispose()-處置
維護內部非託管資源的託管類的手段:Finalize()--終結和Dispose()--處置
非託管資源:原始的作業系統檔案控制代碼,原始的非管理的資料庫串連,非託管記憶體或其他非託管資源。
Finalize()特性:
(1)重寫Finalize()的唯一原因是,c#類通過PInvoke或複雜的COM互通性任務使用了非託管資源(典型的情況是通過System.Runtime.InteropServices.Marshal類型定義的各成員)註:PInvoke是平台叫用服務。
(2)object中有finalize方法,但建立的類不能重寫此方法,若Overide會報錯,只能通過解構函式來達到同樣的效果。
(3)Finalize方法的作用是保證.NET對象能在記憶體回收時清除非託管資源。
(4)在CLR在託管堆上指派至時,運行庫自動確定該對象是否提供一個自訂的Finalize方法。如果是這樣,對象會被標記為可終結的,同時一個指向這個對象的指標被儲存在名為終結隊列的內部隊列中。終結隊列是一個由記憶體回收行程維護的表,它指向每一個在從堆上刪除之前必須被終結的對象。
注意:Finalize雖然看似手動清除非託管資源,其實還是由記憶體回收行程維護,它的最大作用是確保非託管資源一定被釋放。
(5)在結構上重寫Finalize是不合法的,因為結構是實值型別,不在堆上,Finalize是記憶體回收行程調用來清理託管堆的,而結構不在堆上。
Dispose()特性:
(1)為了更快更具操作性進行釋放,而非讓記憶體回收行程(即不可預知)來進行,可以使用Dispose,即實現IDispose介面。
(2)結構和類類型都可以實現IDispose(與重寫Finalize不同,Finalize只適用於類類型),因為不是記憶體回收行程來調用Dispose方法,而是對象本身釋放非託管資源,如Car.Dispose().如果編碼時沒有調用Dispose方法,以為著非託管資源永遠得不到釋放。
(3)如果對象支援IDisposable,總是要對任何直接建立的對象調用Dispose(),即有實現IDisposable介面的類對象都必須調用Dispose方法。應該認為,如果類設計者選擇支援Dispose方法,這個類型就需要執行清除工作。記住一點,如果類型實現了IDisposable介面,調用Dispose方法總是正確的。
(4).net基底類別庫中許多類型都實現IDisposable介面,並使用了Dispose的別名,其中一個別名如IO中的Close方法,等等別名。
(5)using關鍵字,實際內部也是實現IDisposable方法,用ildasm.exe查看使用了using的代碼的CIL,會發現是用try/finally去包含using中的代碼,並且在finally中調用dispose方法。
相同點:
都是為了確保非託管資源得到釋放。
不同點:
(1)finalize由記憶體回收行程調用;dispose由對象調用。
(2)finalize無需擔心因為沒有調用finalize而使非託管資源得不到釋放,而dispose必須手動調用。
(3)finalize雖然無需擔心因為沒有調用finalize而使非託管資源得不到釋放,但因為由記憶體回收行程管理,不能保證立即釋放非託管資源;而dispose一調用便釋放非託管資源。
(4)只有類類型才能重寫finalize,而結構不能;類和結構都能實現IDispose。
5. GC策略
在傳統的堆中,資料結構習慣於使用大塊的空閑記憶體。在其中尋找特定大小的記憶體塊是一件很耗時的工作,尤其是當記憶體中充滿片段的時候。在託管堆中,記憶體被組製成連續的數組,指標總是巡著已經被使用的記憶體和未被使用的記憶體之間的邊界移動。當記憶體被分配的時候,指標只是簡單地遞增—由此的好處是分配操作的效率得到了很大的提升。
當對象被分配的時候,它們一開始被放在Gen0中。當Gen0的大小快要達到它的上限的時候,一個只在Gen0中執行的回收操作被觸發,由於Gen0的大小很小,因此這將是一個非常快的GC過程。這個GC過程的結果是將Gen0徹底的重新整理了一遍。不再使用的對象被釋放,確實正被使用的對象整理並移入Gen1中。
當Gen1的大小隨著從Gen0中移入的對象數量的增加而接近它的上限的時候,一個回收動作被觸發來在Gen0和Gen1中執行GC過程。如同在Gen0中一樣,不再使用的對象被釋放,正在被使用的對象被整理並移入下一個Gen中,大部分GC過程的主要目標是Gen0,因為在Gen0中最有可能存在大量的已不再使用的臨時對象。對Gen2的回收過程具有很高的開銷,並且此過程只有在Gen0和Gen1的GC過程不能釋放足夠的記憶體時才會被觸發。如果對Gen2的GC過程仍然不能釋放足夠的記憶體,那麼系統就會拋出outOfMemoryException異常。
一個帶有終止操作的對象被標記未垃圾時,它並不會被立即釋放。相反,它會被放置在一個終止隊列(finalizationqueue)中,此隊列為這個對象建立一個引用,來避免這個對象被回收。後台線程為隊列中的每個對象執行它們各自的終止操作,並且將已經執行過終止操作的對象從終止隊列中刪除。只有那些已經執行過終止操作的對象才會在下一次記憶體回收過程中被從記憶體中刪除。這樣做的後果是,等待被終止的對象有可能在它被清除之前,被移入更高的Gen中,從而增加它被清除的延遲時間。
需要執行終止操作的對象應當實現IDisposable介面,以便客戶程式通過此介面快速執行終止動作。IDisposable介面包含一個方法-Dispose,這個被Beta2引入的介面,採用一種在Beta2之前就已經被廣泛使用的模式實現。從本質上講,一個需要終止操作的對象暴露出Dispose方法。這個方法被用來釋放外部資源並抑制終止操作。
6. 參考資料
http://www.tudou.com/home/diary_v9913437.html
http://www.cnblogs.com/skywithcloud/archive/2011/08/12/2136789.html
http://msdn.microsoft.com/zh-cn/magazine/cc163491.aspx
http://blog.csdn.net/directionofear/article/details/8034133