九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

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

聯(lián)系客服