您好,登錄后才能下訂單哦!
本篇內容介紹了“怎么提高.NET垃圾回收性能”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
對于GC垃圾回收,很多人不會陌生。我們這里講的是提高.NET垃圾回收機制性能的幾種方法,通過研究.NET垃圾回收機制,可以提高程序執行效率。
介紹和目標
問一下每一個開發人員,在.Net類中清除非托管資源的***位置在哪里?他們中的70%的人員會說放在析構函數。盡管看起來好象是最有希望的位置,但那對性能和內存消耗有巨大的影響。在析構函數中寫清理代碼會導致垃圾回收器再次調用,而且多次(multifold times)影響性能。
為了驗證上面所說,我們先從理論開始,然后我們會真實的看到使用析構函數時如何影響性能。因此我們要理解世代的概念,然后再去看finalize dispose模式。
我相信本文會改變你關于析構函數、dispose 和 finalize處理的看法。
請隨時到 http://www.questpond.com下載我的涵蓋.NET、 ASP.NET、 SQLServer、 WCF、 WPF、WWF的免費500個問題和回答的電子書。
假設
本文使用CLR探測器來探測GC如何工作。如果你對CLR探測器不熟悉,在繼續之前請先閱讀DOTNET1.aspx。
感謝Jeffrey Richter 和 Peter Sollich 先生
讓我們以感謝Jeffery Richter作為本文的開始,因為他深入的解釋了垃圾回收算法如何工作。他曾經寫過兩個關于垃圾回收工作方式的傳奇文章。我很想指出Jeffery Richter在MSDN雜志寫的文章,但因為一些原因并沒有在MSDN顯示出來。所以我給出一個非官方的地址,你可以從http://www.cs.inf.ethz.ch/ssw/files/GC_in_NET.pdf下載PDF格式文章。
同時也感謝Peter Sollich先生,他是CLR性能框架師,為CLR探測器寫了詳細的幫助。當你安裝CLR探測器時,請不要忘記閱讀Peter Sollich寫的詳細幫助文檔。在本文中我們會使用CLR探測器驗證使用finalize對垃圾回收器性能的影響。
非常感謝你們的支持,如果沒有讀你們寫的文章,我就不能完成這篇文章,無論何時我都很樂意聽到你們閱讀文章的評論。
垃圾回收器-幕后英雄
如在介紹中所說,把清理代碼放在析構函數會導致垃圾回收器的兩次調用。許多開發人員會聳聳肩說“我們真的需要去關心垃圾回收器(GC)在后臺做了什么嗎?”,對,如果你寫合適的代碼,我們確實不需要關心垃圾回收。垃圾回收器有保證你的應用程序不受影響的***的算法。但是很多時候,你寫代碼的方式和在你代碼中分配/清理內存資源的方式對垃圾回收算法產生了較大的影響。有時這種影響會導致垃圾回收器(GC)很差的性能,進而導致你應用程序很差的的性能。
因此我們先來看一下在垃圾回收器分配和清理內存時都執行了哪些不同的任務。
假如我們有三個類,類A調用了類B,類B調用了類C。
當應用程序***次執行時,預定義內存分配給應用程序。當應用程序創建這3個對象時,它們被賦于一個內存棧上的地址。你可以從下圖中看到對象創建之前和對象創建之后的內存的樣子。如果還有一個對象D要創建,它會從對象C結束處分配地址。
在內部,垃圾回收器為了知道哪些對象是可達的要維護一個對象圖。所有的對象屬于主應用程序的根對象,根對象同樣維護著哪些對象分配了哪些內存地址。如果一個對象使用了其他的對象,那么這個對象也要保存它使用的對象的內存地址。例如,在我們的示例中的對象A使用了對象B,所以對象A保存了對象B的內存地址。
現在假如對象A從內存中移除,那么對象A的內存被賦于了對象B,對象B的內存被賦于了對象C。內部的內存分配情況如下所示:
隨同內存指針的更新,垃圾回收器需要確保它的內部對象圖也隨著新的內存地址更新了。因此對象圖變成了如下所示的樣子。對垃圾回收器有一些工作要做,它需要確保已經不再使用的對象已經從圖中移除,并且還存在的對象的地址已經在對象樹中全部更新了。
除應用程序自定義對象外,構成對象圖表的還有.Net對象,那些對象的地址也是要更新的。.Net運行時對象的數量非常大,下圖就是一個簡單的Hello World控制臺應用程序創建的對象的數量,對象的數量約有1000個,更新每一個對象的指針是一個很大的任務。
世代算法—今天、昨天和前天
GC(垃圾回收器)使用世代的概念來提升性能。世代的概念是基于人們處理事情的心理的方式。下面的幾點指出人們是如何處理事情的,并且垃圾回收算法是按相同的方式工作:
如果你今天決定要做一些事情,那么很可能今天就把這些事做完。
如果一些事是昨天未決定的,那么很可能這些事情會給予比較低的優先級并且被再一次推遲。
如果一些事是前天未決定的,那么就有很大的可能性這個事被永遠推遲。
GC以同樣的方式思考并且使用下面的假設:
如果一個對象是新創建的,那么它的生命期可能很短。
如果一個對象是原來存在的,它可能會有更長的生命。
所以說,GC做了三個世代的支持(0代,1代和2代)。
0代包括所有新創建的對象,當應用程序創建對象時,這些對象首先被放入0代對象列表中。當0代對象裝滿時,GC需要運行以釋放內存資源,GC開始構建圖表并刪除所有應用程序不再使用的對象。如果一個對象GC不能在0代刪除,那么該對象會被提升為1代。如果在后面的迭代中一個對象不能在1代中刪除,那么它會被提升為2代。.Net運行時支持的***代是2代。
下面是當你運行CLR探測器時關于世代對象的一個簡單顯示。如果你對CLR探測器不了解,請先從DOTNET1.aspx了解CLR的基本知識。
那么,在優化中世代有什么幫助呢
作為世代中的對象,GC會對哪個世代的對象需要被清理做出選擇。如果你記得,前面小節中我們講過關于GC認定對象世代的假設,GC假設新對象具有更短的生命周期。換句話說,GC主要檢查0代的對象,而不是所有世代的所有對象。
如果清理0代對象不能提供足夠的內存,它將繼而清理1代的對象,并依次繼續。這個算法能大幅提升GC的性能。
關于世代的推論
如果有大量的對象在1代或2代區域則說明內存使用沒有優化。
更大的世代1和世代2區域會導致GC算法性能更差。
使用終結器(finalize)/析構函數會導致更多的1代和2代對象
C#編譯器會把析構函數翻譯(重命名)為終結器。如果你使用IDASM查看IL代碼,就會看到析構函數被重命名為終結器(finalize)。所以讓我們先理解為什么實現析構函數會導致更多的對象進入1代和2代區域。現在來看處理器是如何工作的:
當新對象創建時,它們被放到0代。
當0代區域填滿時,GC運行并清理內存。
如果對象沒有析構函數,那么如果它們不再被使用,GC就把它們清理掉。
如果對象有終結(finalize)方法,GC就把它們放到終結隊列中。
如果對象是可達的,它會被放置到Freachable隊列中,如果對象是不可達的,內存將被收回。
GC完成本次迭代工作。
下一次當GC開始工作時,它會進入Freachable隊列檢查對象是否可達,如果Freachable中的對象不可達,內存就會被聲名為可收回的。
換句話說,有析構函數的對象會在內存中存活更長的時間。
讓我們來看下實際的情況,下面是一個簡單的有析構函數的類。
class clsMyClass { public clsMyClass()
{
}
~clsMyClass()
{
}
}
讓我們用CLR探測器來監視創建100*10000個對象時的情況。
for (int i = 0; i < 100 * 10000; i++) { clsMyClass obj = new clsMyClass();
}
如果使用CLR探測器的內存地址報表,會看到大量的對象在1代。
現在去掉析構函數后再做一遍。
class clsMyClass { public clsMyClass()
{
}
}
你可以看到在0代對象大量增加,同時1代和2代對象很少。
如果做一對一的對比,結果如下圖所示:
使用Dispose代替去掉的析構函數
我們可以去掉析構函數而在dispose方法中實現清理代碼。為此要實現‘IDisposable’ 的接口方法,在這寫我們的清理代碼,并如下代碼段所示調用終結方法。
‘SuppressFinalize’指示GC不要調用finalize方法,所以不會發生GC的二次調用。
class clsMyClass : IDisposable { public clsMyClass()
{
}
~clsMyClass()
{
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
現在客戶端要確保它要象如下所示調用
dispose
方法。
for (int i = 0; i < 100 ; i++) { clsMyClass obj = new clsMyClass();
obj.Dispose();
}
下圖是使用析構函數和使用dispose時的0代和1代對象如何分布的對比。你會看到0代內存分配有明顯的提升,這標識著更好的內存分配。
如果開發人員忘記調用Dispose
這不是一個***的世界,我們不能確保在客戶端總是調用了dispose方法。這就是下面的小節中我們要使用Finalize / Dispose模式的原因。
關于這個模式在http://msdn.microsoft.com/en-us/library/b1yfkh6e(VS.71).aspx.有詳細的實現。
下面看起來更象是如何實現finalize / dispose模式。
class clsMyClass : IDisposable { public clsMyClass() { } ~clsMyClass() { // In case the client forgets to call // Dispose , destructor will be invoked for Dispose(false); } protected virtual void Dispose(bool disposing) { if (disposing) { // Free managed objects. } // Free unmanaged objects } public void Dispose() { Dispose(true); // Ensure that the destructor is not called GC.SuppressFinalize(this); } }
代碼解釋:
我們定義了一個帶布爾參數的Dispose方法,該參數說明是從Dispose中調用還是從析構函數中調用。如果是從’Dispose’方法調用,則釋放所有的托管和非托管的資源。
如果該方法是從析構函數中調用,則只釋放非托管的資源。
在dispose方法中我們禁用了finilize的調用,并且用true參數調用了這個dispose方法。
在析構函數中我們使用false值調用dispose函數。換句話說,我們假定GC會處理好托管的資源并用析構函數調用來清理非托管資源。
換句話說,客戶端沒有調用dispose函數,析構函數會照顧清除非托管資源。
“怎么提高.NET垃圾回收性能”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。