国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
CLR 完全介紹: 研究內(nèi)存問題 -- MSDN Magazine, November 2006
發(fā)現(xiàn)和糾正托管應(yīng)用程序中的內(nèi)存問題可能十分困難。 內(nèi)存問題的表現(xiàn)形式多種多樣。例如,您會觀察到,您的應(yīng)用程序的內(nèi)存使用量在不斷增加,最終導(dǎo)致“內(nèi)存不足”(OOM) 異常(您的應(yīng)用程序甚至可能在有大量可用物理內(nèi)存的情況下引發(fā)內(nèi)存不足異常)。但以下任何一種情況均表明內(nèi)存可能出現(xiàn)了問題:
引發(fā) OutOfMemoryException(內(nèi)存不足異常)。
進(jìn)程占用了太多內(nèi)存,您無法確定任何明顯的原因。
似乎垃圾收集功能并沒有快速清理對象。
托管堆碎片過多。
應(yīng)用程序過度占用 CPU。
此專欄討論研究過程,并向您展示如何收集您所需的數(shù)據(jù),以確定您所面臨的應(yīng)用程序中的內(nèi)存問題的類型。此專欄并不包括如何實際修復(fù)您所發(fā)現(xiàn)的問題,但可以為您提供對問題根源的深入分析。
我們首先簡要介紹一下可用于研究托管內(nèi)存問題的最實用的性能計數(shù)器。然后我們會介紹研究過程中常用的工具,接著介紹一系列常見的托管內(nèi)存問題以及如何研究這些問題。
但在開始之前,您首先應(yīng)熟悉一些基本概念:
Microsoft? .NET Framework 中的垃圾收集。有關(guān)詳細(xì)信息,請參閱以下兩個博客記錄:blogs.msdn.com/156626.aspxblogs.msdn.com/234273.aspx.
Windows? 中的虛擬內(nèi)存的工作原理。這包括保留內(nèi)存和分配內(nèi)存的概念。
使用 Windows 調(diào)試程序(WinDbg 和 CDB)。
使用的工具
在開始之前,我們應(yīng)該花點時間討論一下在診斷與內(nèi)存相關(guān)的問題時通常使用的一些工具。
性能計數(shù)器  通常,您會希望首先了解性能計數(shù)器。通過這些計數(shù)器,您可以收集必要的數(shù)據(jù)以確定出現(xiàn)問題的準(zhǔn)確位置。雖然有些其他工具也值得關(guān)注,但是最有用的性能計數(shù)器是 .NET Framework 上介紹的性能計數(shù)器。
調(diào)試程序  在這里我們將使用 WinDbg,該工具隨Windows 調(diào)試工具提供。SOS.dll 中提供的 Son of Strike 擴展 (SOS),用于調(diào)試 WinDbg 中的托管代碼。在啟動了調(diào)試程序并將其附加到托管進(jìn)程(或加載故障轉(zhuǎn)儲)后,您可以通過鍵入以下代碼加載 SOS.dll:
.loadby sos mscorwks如果您正在調(diào)試的應(yīng)用程序使用的是不同版本的 mscorwks.dll,則該命令無法執(zhí)行,那么應(yīng)找到該應(yīng)用程序使用的 mscorwks.dll 版本的 SOS.dll,然后運行以下命令:.load <path_to_sos>\sos.dllSOS.dll 隨 .NET Framework 安裝在 %windir%\microsoft.net\framework\<.NET 版本> 目錄下。SOS.dll 擴展提供了大量用于檢查托管堆的有用命令。有關(guān)所有這些命令的文檔,請參閱SOS 調(diào)試擴展 (SOS.dll)
Windows 任務(wù)管理器  Taskmgr.exe 可方便地發(fā)現(xiàn)超出預(yù)期的內(nèi)存使用情況,并可檢查在一段時間內(nèi)一些簡單的進(jìn)程指標(biāo)的趨勢。注意,taskmgr 中有兩個經(jīng)常被誤解的指標(biāo):Mem Usage (內(nèi)存使用)和 VM Size(虛擬內(nèi)存大小)。Mem Usage 表示的是進(jìn)程工作集(就像進(jìn)程\工作集性能計數(shù)器)。它并不表示所使用的字節(jié)數(shù)。VM Size 反映的是供進(jìn)程使用的字節(jié)數(shù)(就像進(jìn)程\專用字節(jié)數(shù)性能計數(shù)器)。VM Size 可提供關(guān)于您是否面臨內(nèi)存泄漏問題(如果您的應(yīng)用程序存在泄漏,則 VM Size 會隨時間增加)第一線索。
內(nèi)存轉(zhuǎn)儲  在此專欄中介紹的大多數(shù)研究技巧都依賴于內(nèi)存轉(zhuǎn)儲。使用調(diào)試程序的方法有兩種 — 您可以將其附加到正在運行的進(jìn)程,或利用它來分析故障轉(zhuǎn)儲。第一種方法提供了直接的視圖,使您可以了解應(yīng)用程序在運行時的狀況,但該技巧并不總是可行的。
內(nèi)存轉(zhuǎn)儲具有可從實際問題研究階段中分析出數(shù)據(jù)收集階段的優(yōu)點。假設(shè)您希望診斷一臺實際工作的服務(wù)器上的問題,則使用不同的機器脫機分析內(nèi)存轉(zhuǎn)儲可能更容易。
調(diào)試程序中的 .dump /ma dmpfile.dmp 命令可用于創(chuàng)建全內(nèi)存轉(zhuǎn)儲。在研究內(nèi)存問題時確保您始終捕獲全轉(zhuǎn)儲,因為小型轉(zhuǎn)儲并不包含您所需的全部信息。
ADPlus 工具(包含在 Windows 調(diào)試工具中)對于收集故障轉(zhuǎn)儲有很大幫助。有關(guān)詳細(xì)信息,請參閱從 2005 年 3 月起 John Robbins 的Bugslayer 專欄
在本專欄中,我們將假定轉(zhuǎn)儲文件始終加載在調(diào)試程序中(故障轉(zhuǎn)儲可使用 File | Open crash dump 命令加載),或者調(diào)試程序始終附加到進(jìn)程,并且在斷點處停止執(zhí)行。
GC 性能計數(shù)器
每項研究的第一步是收集相關(guān)數(shù)據(jù)并對可能存在問題的位置做出假設(shè)。通常首先從性能計數(shù)器開始。通過 .NET Framework 性能控制臺可使用計數(shù)器,這些計數(shù)器提供了關(guān)于垃圾收集器 (GC) 和垃圾收集流程的有用信息。請注意,.NET 內(nèi)存性能計數(shù)器只有在收集時才更新,而不是根據(jù)性能監(jiān)視器應(yīng)用程序中使用的采樣率進(jìn)行更新。
您應(yīng)該首先檢查 % Time in GC(花在 GC 上的時間的百分比)。它表示自從上次收集結(jié)束后 % Time in GC。如果您發(fā)現(xiàn)此數(shù)值非常高(假設(shè)為 50% 或更高),那么您應(yīng)該檢查一下托管堆內(nèi)部發(fā)生了哪些情況。如果 % Time in GC 沒有超過 10%,那么通常就不必花時間來嘗試減少 GC 用在收集上的時間了,因為這樣做帶來的益處微乎其微。
如果您認(rèn)為您的應(yīng)用程序在執(zhí)行垃圾收集上花費的時間過多,那么下一個要檢查的性能計數(shù)器就是 Allocated Bytes/sec(每秒分配字節(jié)數(shù))。該計數(shù)器顯示了分配速率。不過,該計數(shù)器在分配速率非常低的情況下,并不十分準(zhǔn)確。如果采樣頻率高于收集頻率,該計數(shù)器可能顯示為 0 字節(jié)/秒,因為該計數(shù)器只有在每次收集開始的時候進(jìn)行更新。這并不意味著沒有進(jìn)行分配操作,只是由于在該時間間隔內(nèi)沒有收集發(fā)生,因此計數(shù)器沒有得到更新而已。既然了解到垃圾收集所花費的時間是一個重要的考慮因素,我們將在稍后更詳細(xì)地了解 % Time in GC。
如果您認(rèn)為您要收集大量大型對象(85,000 字節(jié)或更大),則需要檢查大型對象堆 (LOH) 的大小。它與 Allocated Bytes/sec 同時更新。
高分配速率會導(dǎo)致大量收集工作,因此可能 % Time in GC 會比較高。能否減輕這一現(xiàn)象的一個因素為對象通常是否很早就死去,只因為它們通常會在第 0 級收集過程中被收集。要確定對象生命周期對收集有何影響,可檢查各級收集的性能計數(shù)器:# Gen 0 Collections(第 0 級收集次數(shù))、# Gen 1 Collections(第 1 級收集次數(shù))、# Gen 2 Collections(第 2 級收集次數(shù))。這些性能計數(shù)器顯示自進(jìn)程啟動后對各級對象進(jìn)行收集的次數(shù)。第 0 級和第 1 級收集通常開銷很低,因此它們不會對應(yīng)用程序的性能有很大影響。而第 2 級收集器開銷非常大。
首要原則是,各級收集之間合理的比值是每進(jìn)行 10 次第 1 級收集,進(jìn)行一次第 2 級收集。如果您發(fā)現(xiàn)在垃圾收集上花費了大量時間,那可能是由于第 2 級收集的頻率過高造成的。您應(yīng)該檢查上面提到的比值,確保第 2 級收集與第 1 級收集的次數(shù)比值不是太高。
您可能會發(fā)現(xiàn) % Time in GC 很高,但分配速率并不高。如果您分配的許多對象能夠在垃圾收集后保留下來并被提升到下一級,則會出現(xiàn)這種情況。提升計數(shù)器 — 從第 0 級提升的內(nèi)存 (Promoted Memory from Gen 0) 和從第 1 級提升的內(nèi)存 (Promoted Memory from Gen 1) — 可以告訴您提升速率是否存在問題。我們希望避免從第 1 級提升的速率太高。這是因為您可能有大量對象存在時間較長,足以提升到第 2 級,但存在的時間不足以使其保留在第 2 級中。一旦提升到第 2 級,這些對象的收集開銷就要比它們在第 1 級中死去要大。(這種現(xiàn)象被稱為中年危機。有關(guān)詳細(xì)信息,請參閱blogs.msdn.com/41281.aspx。)CLR 分析器 (CLR Profiler) 可幫您了解哪些對象存在時間過長。
第 1 級和第 2 級堆大小的數(shù)值較高往往與提升速率計數(shù)器中的數(shù)值較高相關(guān)。您可以使用第 1 級堆大小和第 2 級堆大小來檢查 GC 堆的大小。有一個第 0 級堆大小計數(shù)器,但它并不用于衡量第 0 級的大小。它用于表示第 0 級的空間預(yù)算 — 意味著在觸發(fā)下一次第 0 級收集之前,在第 0 級中您可以分配的字節(jié)數(shù)。
如果您使用了的大量需要終結(jié)的對象 — 例如,依賴于 COM 組件進(jìn)行一些處理的對象 — 在這種情形下,您可以看一下 Promoted Finalization-Memory from Gen 0(從第 0 級提升的終結(jié)內(nèi)存)計數(shù)器。該計數(shù)器會告訴您由于使用內(nèi)存的對象需要被添加到終結(jié)隊列中而無法立即對其進(jìn)行收集、由此導(dǎo)致無法被重復(fù)使用的內(nèi)存數(shù)量。IDisposable 和 C# 及 Visual Basic? 中的 using 語句可幫助減少在終結(jié)隊列中結(jié)束的對象數(shù)量,從而降低相關(guān)的開銷。
使用 # Total committed Bytes(提供的字節(jié)總數(shù))和 # Total reserved Bytes(保留的字節(jié)總數(shù))可找到關(guān)于堆大小的詳細(xì)數(shù)據(jù)。這些計數(shù)器分別表示當(dāng)前在 GC 堆上提供內(nèi)存和保留內(nèi)存的總數(shù)。(提供的字節(jié)總數(shù)值略微大于實際的第 0 級堆大小 + 第 1 級堆大小 + 第 2 級堆大小 + 大型對象堆大小。)當(dāng) GC 分配一個新堆段時,內(nèi)存將保留給該段,只有在需要時才提供內(nèi)存。因此保留字節(jié)的總數(shù)可以比提供的字節(jié)總數(shù)大。
同樣應(yīng)該檢查一下應(yīng)用程序是否引發(fā)了太多次收集。# Induced GC(引發(fā)的 GC 的數(shù)目)計數(shù)器可以告訴您自進(jìn)程啟動以來引發(fā)了多少次收集。一般而言,不建議您引發(fā)多次 GC 收集。在大多數(shù)情況下,如果 # Induced GC 的數(shù)值較高,您應(yīng)該將其視為 Bug。在大多數(shù)情況下人們引發(fā) GC 是希望削減堆的大小,但這并非理想的選擇。您應(yīng)該了解您的堆大小為何增加。
Windows 性能計數(shù)器
到目前為止,我們已經(jīng)了解了一些最實用的 .NET 內(nèi)存計數(shù)器。但您不應(yīng)忽略其他計數(shù)器的價值。有很多種 Windows 性能計數(shù)器(也可通過 perfmon.exe 查看)為研究內(nèi)存問題提供了有用的信息。
Memory(內(nèi)存)類別下面所列的 Available Bytes(可用字節(jié))計數(shù)器報告了可用的物理內(nèi)存。它可明確地顯示您的物理內(nèi)存是否過低。如果機器的物理內(nèi)存過低,會發(fā)生分頁或者很快會發(fā)生分頁。該數(shù)據(jù)對于診斷 OOM 問題非常有用。
% Committed Bytes in Use(正在使用的字節(jié)百分比)計數(shù)器(同樣位于 Memory 類別下)提供了內(nèi)存使用量與內(nèi)存總量的比值。如果此值非常高(假設(shè)超過 90%),您應(yīng)該開始檢查提供內(nèi)存故障。這明顯表明系統(tǒng)內(nèi)存緊張。
Process(進(jìn)程)類別下的 Private Bytes(專用字節(jié)數(shù))計數(shù)器表示被使用且無法與其他進(jìn)程共享的內(nèi)存數(shù)量。如果您希望了解您的進(jìn)程使用了多少內(nèi)存,您應(yīng)該監(jiān)視此計數(shù)器。如果您遇到了內(nèi)存泄漏問題,專用字節(jié)數(shù)會隨時間增加。該計數(shù)器還可明顯地表明了您的應(yīng)用程序?qū)φ麄€系統(tǒng)的影響 — 使用大量專用字節(jié)會對機器有很大影響,因為內(nèi)存無法與其他進(jìn)程共享。這在某些情形下至關(guān)重要,如終端服務(wù),在這種情形下您需要使用戶會話之間共享的內(nèi)存量達(dá)到最大。
確認(rèn)托管進(jìn)程中的 OOM 異常
性能計數(shù)器可向您明確表示您是否正在面臨內(nèi)存問題。但在大多數(shù)情況下,只有在您的應(yīng)用程序中出現(xiàn)內(nèi)存不足異常的情況下才能檢測到內(nèi)存問題。因此您需要了解您實際上是否正在發(fā)生由托管代碼引起的 OOM 異常。
在您加載了 SOS.dll 后,可在調(diào)試程序中鍵入以下命令:
!pe這是 !PrintException 的縮寫形式。它將輸出線程(如果有)上最后的托管異常,無需參數(shù)。圖 1 中顯示了 OOM 托管異常的一個示例。
如果當(dāng)前線程上沒有托管異常,您就不必了解 OOM 來自哪個線程了。要了解這一點,請在調(diào)試程序中鍵入以下代碼:
~*kb在這里,kb 是 Display Stack Backtrace(顯示堆?;厮荩┑目s寫。它列出了所有線程及其堆棧的調(diào)用(參見圖 2)。在輸出中,查找存在異常調(diào)用的線程和堆棧。最簡便的方法就是查找 mscorwks::RaiseTheException。
mscorwks 中的 RaiseTheException 函數(shù)的參數(shù)是托管的異常對象。您可以使用 !pe 對其進(jìn)行轉(zhuǎn)儲。此外 !pe 還有一個 –nested 選項,將對除頂級異常之外的所有嵌套異常進(jìn)行轉(zhuǎn)儲。
找出導(dǎo)致 OOM 的線程的另一種方法是使用 SOS 的 !threads 命令。所顯示的表的最后一欄將包含各個線程最近引發(fā)的托管異常。
如果您使用這些技巧沒有找到 OOM 異常,則沒有托管 OOM,您所面臨的異常由本機代碼引發(fā)。在這種情況下,您需要關(guān)注您的應(yīng)用程序使用的本機代碼(關(guān)于此問題的討論超出了本專欄的范圍)。
確定導(dǎo)致 OOM 異常的原因
在您確認(rèn)了這是 OOM 異常之后,您應(yīng)該檢查導(dǎo)致 OOM 的原因。在兩種情形下會出現(xiàn)托管 OOM — 進(jìn)程耗盡了虛擬內(nèi)存,或者沒有足夠的物理內(nèi)存可提供。
GC 需要為其段分配內(nèi)存。當(dāng) GC 決定它需要分配一個新段時,它會調(diào)用 VirtualAlloc 以保留空間。如果段沒有連續(xù)的足夠大的可用塊,則調(diào)用失敗,GC 無法滿足新的內(nèi)存請求。
在調(diào)試程序中,!address 命令可為您顯示虛擬內(nèi)存的最大可用區(qū)域。輸出將類似于:
0:119>!address -summary... [omitted]Largest free region: Base 54000000 - Size 03b600000:119>? 03b60000Evaluate expression: 62259200 = 03b60000如果在 32 位操作系統(tǒng)上進(jìn)程可使用的最大可用虛擬內(nèi)存塊小于 64MB(64 位操作系統(tǒng)上小于 1GB),則耗盡虛擬內(nèi)存可能會導(dǎo)致 OOM(Out of Memory,內(nèi)存不足)。(在 64 位操作系統(tǒng)上,應(yīng)用程序不大可能耗盡虛擬內(nèi)存空間。)
如果虛擬內(nèi)存的碎片過多,則進(jìn)程可能會耗盡虛擬空間。通常托管堆不會產(chǎn)生虛擬內(nèi)存碎片,但也有可能會出現(xiàn)這種情況。例如,如果應(yīng)用程序創(chuàng)建了大量的臨時大型對象,導(dǎo)致 LOH 不斷獲得和釋放虛擬內(nèi)存段,那么就有可能出現(xiàn)這種情況。
!eeheap –gc SOS 命令將為您顯示每個垃圾收集段的起始位置。您可以將其與 !address 的輸出關(guān)聯(lián)考慮,以確定虛擬內(nèi)存的碎片是否由托管堆造成。
以下是其他一些可能導(dǎo)致產(chǎn)生虛擬內(nèi)存碎片的常見情況。
總是加載和卸載許多小的程序集。
由于 COM 互操作而加載大量的 COM DLL。
在托管堆中沒有同時加載程序集和 COM DLL??赡軐?dǎo)致這一問題的一種常見情形是,在啟用了“debug”配置標(biāo)志的情況下對 ASP.NET 站點進(jìn)行編譯。這會導(dǎo)致每個頁在其各自的程序集中進(jìn)行編譯,可能會產(chǎn)生足以引發(fā) OOM 問題的虛擬內(nèi)存空間碎片。
保留內(nèi)存不需要操作系統(tǒng)提供物理內(nèi)存。只有在 GC(垃圾收集器)提供物理內(nèi)存時才會分配物理內(nèi)存。如果使用非常低的物理內(nèi)存來運行系統(tǒng),則應(yīng)該會出現(xiàn) OOM 異常。檢查您的物理內(nèi)存是否過低的一種簡單方法就是打開 Windows 任務(wù)管理器,查看“性能”選項卡上的“內(nèi)存使用”區(qū)域。
圖 3 顯示系統(tǒng)總共提供了 1981304 KB 內(nèi)存,總內(nèi)存數(shù)為 2518760 KB。當(dāng)提供的內(nèi)存總數(shù)接近總內(nèi)存數(shù)時,系統(tǒng)就會耗盡可用內(nèi)存。
圖 3 任務(wù)管理器里查看可用內(nèi)存(Click the image for a larger view)
GC 并非一次提供整個段。而是根據(jù)需要以多個塊的形式提供段。(注意,托管堆提供的字節(jié)數(shù)由 # Total committed Bytes 表示,而不是 # Bytes in all Heaps(所有堆中的字節(jié)數(shù))。這是因為 # Bytes in all Heaps 中包含的第 0 代大小并非第 0 代中使用的實際內(nèi)存,而是其預(yù)算。)
您可以使用用戶模式分析器(如 CLR 分析器)了解哪些對象導(dǎo)致了如此高的內(nèi)存使用量。但在某些情況下,運行分析器的開銷讓人無法接受 — 例如,當(dāng)需要在生產(chǎn)服務(wù)器上調(diào)試問題時就會這樣。在這種情況下,一種替代方法就是采取內(nèi)存轉(zhuǎn)儲,然后使用調(diào)試器對其進(jìn)行分析。那么接下來介紹一下如何使用調(diào)試器來分析托管堆。
衡量托管堆的大小
在衡量托管堆大小時,您首先需要了解的是何時進(jìn)行衡量。應(yīng)該在垃圾收集之前、之后還是收集過程中進(jìn)行衡量?衡量堆大小的最佳時間始終是在第 2 代收集結(jié)束的時候,因為進(jìn)行第 2 代收集時會收集整個堆。
要在第 2 代垃圾收集結(jié)束時查看對象,可在調(diào)試器中設(shè)置以下斷點(對于服務(wù)器上的垃圾收集,只需將 WKS 替換為 SVR):
bp mscorwks!WKS::GCHeap::RestartEE "j(dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2)‘kb‘;‘g‘"
現(xiàn)在您會在第 2 代垃圾收集結(jié)束時停止,下一步就是查看托管堆上的對象。這些對象是在垃圾收集后保留下來的對象,您希望了解它們?yōu)槭裁幢槐A粝聛淼脑颉?div style="height:15px;">
!dumpheap –stat 命令可對托管堆上的對象進(jìn)行完整的轉(zhuǎn)儲。(因此,如果堆較大,!dumpheap 命令可能需要一段時間才能完成。)!dumpheap 命令生成的列表按類型和使用的內(nèi)存量進(jìn)行分類。這意味著您可以從最后的幾行開始分析,因為這幾行代表占用了大部分空間的對象。
圖 4 中的示例中,字符串占用了大部分空間。如果字符串是問題的根源,那么這種問題往往容易解決。字符串的內(nèi)容可反映出其來源。
您還可以在存儲桶中查看字符串。例如,您可以檢查大小在 150 至 200 之間的所有字符串,如圖 5 中所示。本例中的大量字符串都非常相似。因此,與其保留這么多字符串,不如將其共同的部分(“PendingOrder-”)和那些數(shù)字分開來保存,這樣做會更有效。
我們曾經(jīng)多次看到過托管堆包含重復(fù)了數(shù)千次的相同字符串的情況。結(jié)果是產(chǎn)生了一個字符串占用大量內(nèi)存的龐大工作集。在這種情況下,使用字符串駐留往往更好。
對于其他并不像字符串這樣明顯的類型,您可以使用 !gcroot 來了解這些對象為何處于活動狀態(tài)(請參見圖 6)。注意,如果對象圖非常大,!gcroot 命令的執(zhí)行可能需要較長時間。
除了托管堆上保留下來的對象,為您的進(jìn)程提供的內(nèi)存中還包含在第 0 代中分配的內(nèi)存。如果允許第 0 代在下一次垃圾收集發(fā)生前增大,您還可能會觀察到由于此問題而導(dǎo)致的內(nèi)存使用量變大。這種情況在 64 位 Windows 系統(tǒng)上比 32 位系統(tǒng)更常見。!eeheap –gc SOS 命令將為您顯示第 0 代的大小。
如果對象保留下來會怎樣?
有時,開發(fā)人員認(rèn)為他們的某些對象應(yīng)該處于死狀態(tài),但 GC 似乎并沒有把這些對象清理掉。導(dǎo)致這種現(xiàn)象的最常見原因是:
對于這些對象強烈的引用仍然存在。
在最后一次收集對象的代時,對象還未處于死狀態(tài)。
對象處于死狀態(tài),但還沒有觸發(fā)對這些對象所在的代的收集。
對于第一種和第二種情形,您可以使用 !gcroot 檢查是否有強烈的引用使對象保留了下來。人們往往忽略的一種可能性就是,對象在終結(jié)器線程受阻的情況下由于尚處于終結(jié)隊列中而被保留下來,受阻的原因是無法調(diào)用單線程單元 (STA) 線程,因此不會抽取消息來運行終結(jié)器(更多詳細(xì)信息請參閱support.microsoft.com/kb/828988)。您可以通過添加以下代碼確定是不是這一問題:
GC.Collect();GC.WaitForPendingFinalizers();GC.Collect();上述代碼可修復(fù)該問題,因為 WaitForPendingFinalizer 可抽取消息。不過,一旦確認(rèn)是這一問題后,您應(yīng)改用 Thread.Join,因為 WaitForPendingFinalizer 是非常重型的線程。
您還可以通過運行以下 SOS 命令確認(rèn)是不是這一問題:
!finalizequeue查看準(zhǔn)備終結(jié)的對象數(shù) — 而非“可終結(jié)對象數(shù)”。當(dāng)終結(jié)器受阻時,終結(jié)器線程會顯示當(dāng)前正在運行哪個終結(jié)器(如果有)。(請參見圖 7 的終結(jié)隊列示例。)
了解終結(jié)器線程的一個簡便方法就是查看 !threads-special 的輸出。圖 8 所示的堆棧顯示了終結(jié)器線程通常的狀態(tài) — 它正在等待一個事件指示有終結(jié)器要運行。當(dāng)某個終結(jié)器受阻時,您將看到該終結(jié)器正在運行。
第三個原因不應(yīng)該是問題所在。通常,除非您手動引發(fā)了垃圾收集,否則只有在 GC 認(rèn)為這樣做有效時才會觸發(fā)收集。這意味著某個對象可能已經(jīng)處于死狀態(tài),但不會立即回收其占用的內(nèi)存。但當(dāng)系統(tǒng)物理內(nèi)存非常緊張時,GC 操作會變得更加積極主動。
托管堆上的碎片是否會造成問題?
在查明內(nèi)存問題時,碎片是要關(guān)注的主要因素。它之所以重要是因為您需要了解托管堆上浪費了多少空間。托管堆的碎片數(shù)量由可用對象所占用的空間量來表示。您可以使用 !dumpheap 命令了解托管堆上有多少可用內(nèi)存,如下所示:
0:000>!dumpheap -type Free -stattotal 230 objectsStatistics:MT Count TotalSize Class Name00152b18 230 40958584 FreeTotal 230 objects在此例中,輸出結(jié)果表明,有 230 個可用對象,總共大約 39MB。因此,此堆的碎片有 39MB。
當(dāng)您嘗試確定碎片是否會造成問題時,您需要了解碎片對于不同代意味著什么。對于第 0 代,碎片不構(gòu)成問題,因為 GC 可以在碎片空間中進(jìn)行分配。對于第 1 代和第 2 代,碎片可能會造成問題。要在第 1 代和第 2 代中使用碎片空間,GC 必須收集和提升對象以填補這些間隙。但由于第 1 代的大小不會超過一個段,因此您通常需要關(guān)注的是第 2 代。
過多的釘住通常是造成碎片太多的原因。.NET Framework 2.0 在減少由于釘住而導(dǎo)致碎片的問題方面做了大量改進(jìn)(有關(guān) NET Framework 2.0 中 GC 改進(jìn)方面的詳細(xì)信息,請參閱以下網(wǎng)址的博客內(nèi)容:blogs.msdn.com/476750.aspx),但如果應(yīng)用程序仍是過多的使用釘住,則還是會看到大量的碎片。您可以使用一個 SOS 命令 !gchandles 來查看釘住句柄的數(shù)量(請參見圖 9)。還可以使用 !objsize 了解哪些對象被釘住,如圖 10 中所示。
LOH 中的碎片是有意而為之的,因為我們沒有對 LOH 進(jìn)行壓縮。這并不意味著 LOH 上的分配與使用 NT 堆管理器的 malloc 相同!由于 GC 的工作特點,彼此相鄰的可用對象會自然地折疊成一個大的可用空間,可用于滿足大型對象的分配請求。
衡量在垃圾收集上花費的時間
開發(fā)人員往往需要了解 GC 每次進(jìn)行收集時所花費的時間。在軟件實時情形下,該數(shù)據(jù)往往很重要,因為在這種情形下對于應(yīng)用程序必須遵守的響應(yīng)時間等條件有一定限制。這當(dāng)然是一個重要的考慮因素,因為在垃圾收集上花費過多時間就意味著占用了 CPU 用于實際處理的時間。
了解在垃圾收集上花費的時間的最簡便的方法就是查看 % Time in GC 性能計數(shù)器。該計數(shù)器在收集結(jié)束時更新,顯示剛剛完成的 GC 所花費的時間與自上次 GC 結(jié)束后所經(jīng)歷時間的比值。如果在采樣間隔內(nèi)沒有發(fā)生收集,則該計數(shù)器不更新,您看到的值與上次相同。由于您知道性能監(jiān)視器應(yīng)用程序中的采樣間隔(PerfMon 中默認(rèn)的采樣間隔是 1 秒),您可以粗略計算出時間。
圖 11 給出了一些垃圾收集數(shù)據(jù)示例。其中您將看到在第二個和第三個間隔中發(fā)生了第 0 代收集。由于我們并不準(zhǔn)確了解在這些間隔期間收集何時發(fā)生,因此這個方法并非 100% 準(zhǔn)確。但它對于預(yù)測 GC 所花費的時間非常有用。
考慮以下示例,這對于第十一次 GC 而言是最極端的情形。假設(shè)第十次第 0 代收集在第二個間隔開始時完成,第十一次第 0 代收集在第三個間隔結(jié)束時完成。這意味著兩次收集結(jié)束之間的時間大約是兩個采樣間隔,或者說是兩秒。% Time in GC 計數(shù)器顯示為 3%,因此第十一次第 0 代收集只花費了 2 秒的 3%(或 60 毫秒)。
研究高 CPU 使用
當(dāng)收集發(fā)生時,CPU 使用應(yīng)該較高,從而使 GC 可以盡快完成。圖 12 顯示了收集始終會造成非常高的 CPU 使用的一個示例。% Process Time(進(jìn)程時間百分比)計數(shù)器中的所有峰值直接與 % Time in GC 的變化相對應(yīng)。顯然,在實際中永遠(yuǎn)不會發(fā)生這種情況,因為除了 GC 使用 CPU 之外,其他進(jìn)程也將使用 CPU。要確定還有哪些進(jìn)程在占用 CPU 周期,您可以使用 CPU 分析器查看哪些功能占用了大多數(shù) CPU 時間。
圖 12 當(dāng)收集引起 CPU 使用之時(Click the image for a larger view)
如果實際上您發(fā)現(xiàn) GC 占用了太多 CPU 時間,則說明收集的發(fā)生頻率過高或者收集過程所花費的時間太長??紤]當(dāng)收集由分配觸發(fā)時的情況。分配速率是決定收集觸發(fā)頻率的主要因素。
圖 13 當(dāng)收集被分散開后產(chǎn)生較不準(zhǔn)確的數(shù)據(jù)
當(dāng)收集開始時,通過加上第 0 代和 LOH 中的已分配字節(jié)數(shù)對 Allocated Bytes/sec 計數(shù)器進(jìn)行更新。由于該計數(shù)器以速率表示,因此您看到的實際數(shù)值是最后兩個數(shù)值之間的差除以時間間隔的值。例如,圖 13 說明了如果采樣速率為 1 秒并且收集只在經(jīng)過一定間隔后才發(fā)生的情況。當(dāng)收集發(fā)生時,性能計數(shù)器的更新如下:
Allocation = 850-250=600KBAlloc/Sec = 600/3=200KB/sec
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
用IBM HeapAnalyzer和MOD4J分析Java內(nèi)存泄漏
追蹤內(nèi)存泄漏 | TechTarget IT專家網(wǎng)
Java虛擬機回憶錄
探查內(nèi)存不足(內(nèi)存泄露)問題
JVM性能優(yōu)化
Android內(nèi)存管理&Memory Leak&OOM分析
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服