.NET Framework 應用程式的實際執行環境偵錯
偵錯記憶體問題

摘要:本章說明使用者在使用 ASP.NET 應用程式時,如何解決可能經歷到的偵錯記憶體耗用問題。首先,本章討論在 .NET 中是如何管理記憶體,特別是記憶體回收行程 (GC) 如何重新取得未使用的記憶體。接下來將藉由案例,逐步解說如何偵錯記憶體耗用。

由於作業系統版本和 Runtime 版本的不同,雖然問題相同,但在您機器上可能會出現不一樣的結果,不過輸出應該很類似,而且偵錯觀念是相通的。而且,由於所有的 .NET Framework 應用程式都使用相同的記憶體架構,所以您可以將從 ASP.NET 中學習到的記憶體管理觀念,應用到其他 .NET 環境,例如像主控台應用程式、Microsoft® Windows® 作業系統應用程式和 Windows 服務等。

目錄

.NET 記憶體管理和記憶體回收

案例:記憶體耗用

結論

.NET 記憶體管理和記憶體回收

傳統上,C 和 C++ 程式,因為開發人員必須手動配置及釋放記憶體,所以容易發生記憶體遺漏的問題。但在 Microsoft® .NET 中,就不必這樣做,因為 .NET 使用「記憶體回收」,可以自動重新取得未使用的記憶體。這使得記憶體的使用更安全且更有效率。

記憶體回收行程 (GC) 使用參考,追蹤佔用記憶體區塊的物件。當物件設定成 Null 或是不再位於範圍內時,GC 會將物件標記為可收回。GC 可以將這些可收回物件所參考到的記憶體區塊,傳回給作業系統。

GC 的效能優勢,來自於物件回收方式的不同,以及能夠立即執行物件大量回收的能力。GC 傾向於較一般記憶體管理常式,例如 Windows 架構作業系統常式,使用更多的記憶體。

.NET 中的 GC,使用 Microsoft Win32® VirtualAlloc() 應用程式發展介面 (API) 來預留其堆積的記憶體區塊。.NET Managed 堆積是一大塊連續的虛擬記憶體。GC 首先預留虛擬記憶體,然後在 Managed 堆積增加時,再認可配置記憶體。GC 追蹤 Managed 堆積末端的下一個可用位址,並在這個位置置入下一個配置要求。因此,所有 .NET Managed 記憶體配置都會一一放入 Managed 堆積內。這樣可大幅縮短配置時間,GC 不再需要像一般堆積管理員一樣,不斷地在許多未使用或連續的記憶體區塊中,搜尋大小適中的可用區塊。一段時間以後,因有物件被刪除,Managed 堆積中會形成空洞。當發生記憶體回收時,GC 會壓縮堆積,直接使用記憶體複製功能移動配置來填滿空洞。[圖 2.1] 顯示這種執行方式。

ms954591.f02dbg01(zh-tw,MSDN.10).gif

[圖 2.1]:記憶體回收行程如何壓縮堆積

如需 .NET 記憶體回收機制的詳細資訊,請參閱下列參考文件:

  • 2000 年 11 月刊載在 《MSDN 雜誌》中,由 Jeffrey Richter 所著的〈Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework〉(http://msdn.microsoft.com/magazine/bb985010.aspx) (英文)。
  • 2000 年 12 月刊載在《MSDN Magazine》中,由 Jeffrey Richter 所著的〈Garbage Collection—Part 2: Automatic Memory Management in the Microsoft .NET Framework〉(http://msdn.microsoft.com/magazine/bb985011.aspx) (英文)。
  • 由 Jeffrey Richter 所著的《Applied Microsoft .NET Framework Programming》中,第 19 章〈Automatic Memory Management (Garbage Collection)〉( (Microsoft Press,2002 年)。

層代

為了改善記憶體管理效能,GC 以存留期為基礎,將物件分為不同層代。發生回收時,會先收集最新層代的物件。如果這樣還沒有釋放足夠的記憶體,接下來就會收集較舊的層代。層代的使用表示 GC 在任一時間內只需用到一部份的已配置物件。

GC 目前使用三個層代,分別編號為 0、1、和 2。接受配置的物件一開始屬於層代 0。回收包括有 0、1、或 2 三種深度。所有已存在的物件在經過一次回收後,都會從 0 的深度升級到層代 1。在經過回收層代 0 和 1 之後,原來具有深度 1 的已存在物件,將移動至層代 2。[圖 2.2] 顯示層代間的轉換如何發生。

ms954591.f02dbg02(zh-tw,MSDN.10).gif

[圖 2.2]:在多次回收期間層代之間的轉換

一段時間後,較高層代中會填滿最舊的物件。這些比較高的層代應該較為穩定,並且需要較少的回收;因此,記憶體複製在比較高的層代中較少發生。

當特定層代達到記憶體臨界值時,就會執行回收。在 .NET 1.0 版的實作中,層代 0、1、和 2 的初始臨界值分別是 256 千位元組 (KB)、2 百萬位元組 (MB)、和 10 MB。應注意,GC 可以根據應用程式的配置模式動態調整這些臨界值。大於 85 KB 的物件會自動放入大型物件堆積中,稍後本章中將討論這個主題。

GC 使用物件參考決定 Managed 堆積中特定區塊的記憶體是否可以回收。與其他 GC 實作不同,每個已配置的區塊上並沒有堆積旗標,指示區塊是否可回收。對於每個應用程式而言,GC 維持一個追蹤應用程式參考物件的參考樹狀結構。[圖 2.3] 顯示這個樹狀結構。

ms954591.f02dbg03(zh-tw,MSDN.10).gif

[圖 2.3]:根參考樹狀結構

GC 認為,如果物件至少有一個父物件參考到它,則表示該物件已「生根」。.NET 中的每個應用程式都有一組「根」,包括全域和靜態物件,以及相關執行緒堆疊和動態執行個體化物件。在執行記憶體回收之前,GC 從根開始,向下建置所有變數參考的樹狀結構。GC 會建置一份主清單,其中包含所有現存的物件,然後逐層搜尋 Managed 堆積,找尋這個現存物件清單中所沒有的物件。

用這種方式判斷物件是否存在,相較於在記憶體區塊標頭中使用簡單的旗標或使用參考計數器,看起來是比較昂貴,但這樣做絕對可以確保正確性。例如,物件參考計數器,可能讓人過度參考或輕視其參考價值,而堆積旗標,則可能在記憶體區塊有現存參考時,卻錯誤地設定成已刪除。Managed 堆積,會在回收前列舉所有現存物件並建置所有參考物件的清單,來避免這些問題。另外,更好的是,這個方法也解決掉循環記憶體參考的問題。

如果物件存在有現存參考,該物件會被稱為「強式生根」。.NET 也引入了「弱式生根」參考的觀念。弱式參考為程式設計者提供一種指向 GC 的方法,藉由此方法,設計者能夠存取物件,但又不會使物件無法回收。這種物件在 GC 回收它之前可以一直存在。例如,您可以配置一個大型物件,在不必完全刪除或回收它的情況下,可保有這個物件並盡可能地重複利用,直到有記憶體壓力時再清除該 Managed 堆積。因此,弱式參考有點類似像快取的功用。

大型物件堆積

.NET 記憶體管理員可將 85,000 位元組以上的空間配置為個別堆積,該堆積稱為「大型物件堆積」。這個堆積由與主要 Managed 堆積分開的一連串虛擬記憶體區塊所組成。因為回收需要移動記憶體,而移動大區塊的記憶體成本較高,所以較大物件若使用個別堆積,可使主要 Managed 堆積的記憶體回收更有效率。但是,當您在 .NET 中執行大範圍的記憶體配置時,就必須考慮到一個問題,就是大型物件堆積從不壓縮。

例如,如果您將 1 MB 的記憶體配置為單一區塊,大型物件堆積的大小就會擴大為 1 MB。當您釋放這個物件的時候,大型物件堆積不會解除認可該虛擬記憶體,如此一來,堆積的大小會維持在 1 MB。如果您稍後配置另一個 500 KB區塊,新區塊會配置在屬於大型物件堆積的 1 MB 記憶體區塊內。在處理程序存留期間,大型物件堆積總是不斷增加,以儲存所有目前參考到的大區塊配置,但是當物件釋放後,即使發生記憶體回收,也不會縮小。[圖 2.4] 顯示大型物件堆積的範例。

ms954591.f02dbg04(zh-tw,MSDN.10).gif

[圖 2.4]:大型物件堆積

案例:記憶體耗用

在介紹過 .NET 記憶體管理和記憶體回收機制之後,現在讓我們來瞭解這些機制是如何應用在 ASP.NET 應用程式中。以下案例將探究該如何偵錯記憶體耗用問題。您可能已經發現,記憶體遺漏發生的原因,是因為沒有將動態配置的記憶體解除配置。小幅度的記憶體遺漏可能不會受到重視,而且也只是造成小損傷,但是大範圍的記憶體遺漏,由於流失可用的記憶體,則會嚴重影響到效能。除此之外,還有其他與記憶體相關的問題,不能算是「真正的」記憶體遺漏,但已顯現出記憶體遺漏的徵兆。以下案例的焦點集中在後者,與記憶體相關的問題上。

這裡有一些典型的客戶案例,顯示記憶體可能發生問題:

  • 案例 1:銷售商品的電子商務網站。瀏覽器客戶抱怨遺失資料,而且看到「伺服器應用程式無法使用」的錯誤訊息。他們必須再登入一次,而且不知道發生的原因。除此之外,當記憶體用量增加時,伺服器效能會減慢。
  • 案例 2:提供影像檔上載功能的網站。當瀏覽器用戶端上載一個大檔案時,處理程序回收而且檔案上載失敗。

分析思考流程

在嘗試偵錯記憶體相關問題之前,您需要先進行多項檢查。[圖 2.5] 顯示您在進行耗用問題的疑難排解時,可以遵循的流程圖。

ms954591.f02dbg05(zh-tw,MSDN.10).gif

[圖 2.5]:疑難排解記憶體耗用問題的流程圖

雖然稍後才會逐步解說思考流程,但是先讓我們較仔細地看一下流程圖。

出現的錯誤訊息是否暗示發生記憶體遺漏?

如果您使用 Microsoft Internet Information Services (IIS) 5.x,請檢查看是否出現應用程式事件記錄檔錯誤,指示 ASP 處理程序已回收。如果發生這個問題,您將看到類似以下的錯誤訊息:「aspnet_wp.exe (PID: 1234) 已經回收,因為記憶體耗用超過 n MB (可用 RAM 的百分之 n)」。

我的記憶體用到哪兒去了?

下個步驟要判斷哪個流程用掉過多的記憶體。類似像系統監視器 (Windows 2000 中稱為效能監視器) 或工作管理員的工具,可使這些處理程序個別獨立。一旦您知道是哪個流程有問題,就可以執行 AutoDump+ (ADPlus),建立 Aspnet_wp.exe 處理程序的完整記憶體傾印,然後使用 WinDbg 偵錯工具和 SOS.dll 偵錯工具擴充程式,檢查 Managed 記憶體和原生記憶體之間的不同。稍後將在本章中討論這個主題。

如果專案在受控管的環境 (例如在開發、品保(QA) 或測試) 下執行,就可以在 -hang 模式下重現問題並執行 ADPlus,以產生完整的 Aspnet_wp.exe 處理程序的記憶體傾印。若希望處理程序在經過較長時間後再被回收,您可以使用 machine.config 中 <processModel> 項目的 memorylimit 屬性,將預設 60% 的限制提高。

您也可以使用 .NET API 或系統監視器收集更多的資訊。例如,您可以利用系統監視器,找出能夠表示是發生 .NET Managed 記憶體遺漏或原生記憶體遺漏的模式。

原生記憶體耗用

如果在 [.NET # Bytes in all Heaps] 計數器保持不變的情況下,系統監視器的 [Private Bytes (私用位元)] 計數器增加,這就表示發生了原生記憶體耗用。這些計數器將在本章稍後的「記憶體耗用逐步解說」中更詳細地討論。

Managed 記憶體耗用

使用系統監視器時,如果 [Private Bytes (私用位元)] 計數器和 [.NET # of Bytes in all Heaps] 計數器,都以相同比例增加,就表示發生了 Managed 記憶體耗用。查看已配置的 Managed 記憶體大小,並考慮當時 GC 在執行的工作。查看傾印檔案後,使用 SOS 命令列出 Managed 堆積的大小,並將它與傾印檔案總大小相較。想一下您可以從大型物件堆積和層代中看出什麼端倪。找出是大型物件 (85 KB 以上) 或小物件消耗掉大部份的記憶體。如果是前者,則查看大型物件堆積的細節。如果是後者,則考慮其中包含的層代 0、1、和 2。若是大型物件,判斷他們是否已生根,以及他們是否應該生根。如果他們尚未生根,就可以考慮回收。最後判斷他們是否正確地回收。

藉由 WinDbg 和 SOS.dll,可能很難看到全部小物件的細節,特別是如果有很多小物件的時候。在這種情況下,較簡單的做法是使用 Allocation Profiler 來查看大型物件和小物件的細節。〈記憶體耗用逐步解說〉中使用所有這些工具,診斷記憶體耗用問題。

記憶體耗用逐步解說

下列的逐步解說呈現一個簡化的真實案例:ASP.NET 應用程式配置太多記憶體而導致回收,因為它超過處理程序模型所允許的記憶體限制。這個逐步解說的目的,是要介紹一種能夠協助您解決這類問題的技術。實際執行環境中發生的案例可能不像這個簡化範例一樣的單純清楚,但是您可以利用類似的技術,找出記憶體耗用的起因。

這個逐步解說將說明的大型記憶體遺漏問題,是因為流失可用的記憶體,而導致嚴重影響效能。範例中 ASP.NET 網頁配置了大型物件 (每個 20 MB),並將他們快取到 ASP.NET 的快取記憶體中。依照預設,當記憶體使用超過 60% 的可用 RAM 時,ASP.NET 處理程序就會被回收。這個逐步解說的重點是要判斷,到底是哪個處理程序用掉太多的記憶體,以及發生的原因。

在這個案例中,您將執行下列步驟:

  1. 瀏覽 http://localhost/debugging/memory.aspx 的 ASP.NET 網頁,並思索一下處理程序模型表中所顯示內容的價值。
  2. 跟隨流程圖的思考流程,找出瀏覽器和應用程式事件記錄檔所呈現的錯誤。
  3. 使用 WinDbg 和 SOS.dll,建立傾印檔案並檢視傾印資料。
  4. 檢查 ASP.NET 工作處理程序的 Managed 記憶體,並判斷哪一個物件耗用大部份的記憶體。
  5. 當您在傾印中找出證據,確定可能出現問題的範圍後,先查看其原始程式碼。

基礎檢視

首先,收集一些基礎資料,便於逐步比較原生記憶體耗用和 Managed 記憶體耗用的差異。如需如何下載範例網頁的指示,請參閱第 1 章〈介紹 .NET Framework 應用程式的實際執行環境偵錯〉。

若要檢視 Memory.aspx

  • 開啟瀏覽器視窗,然後瀏覽 http:// localhost/debugging/memory.aspx。

    注意網頁顯示的資訊,尤其是表格的欄位和值。

下列瀏覽器檢視提供一些參考基礎,可供您比較耗用記憶體物件建立前後的差異。

ms954591.f02dbg06(zh-tw,MSDN.10).gif

[圖 2.6]:基礎瀏覽器資料

下列表格說明顯示欄位所代表的意義:

表 2.1:Memory.aspx 欄位說明

欄位說明
開始時間 (StartTime)這個 Aspnet_wp.exe 處理程序開始的時間
存留期 (Age)處理程序執行了多久時間
處理程序 ID (ProcessID)指定給處理程序的識別碼
要求計數 (RequestCount)已完成要求的數量,初始值為零。
狀態 (Status)處理程序的目前狀態:如果是 [Alive (資源存在)],表示處理程序正在執行;如果是 [Shutting Down (正在關閉)],表示處理程序開始要關閉;如果是 [ShutDown (關閉)],表示處理程序接收到 IIS 處理程序的關閉訊息,已正常關閉;如果是 [Terminated (結束)],表示 IIS 處理程序強迫本處理程序結束。
關閉原因 (ShutdownReason)處理程序關閉的原因:如果是 [Unexpected (未預期)],表示處理程序無預警地自動關閉;如果是 [Requests Limit (要求限制)],表示處理程序執行的要求超過所允許的範圍;如果是 [Request Queue Limit (要求佇列限制)],表示指定給處理程序的要求超過佇列允許的數量;如果是 [Timeout (逾時)],表示處理程序重新啟動,因為它存留的時間超過所允許的範圍;如果是[Idle Timeout (閒置等待時間)],表示處理程序超過所允許的閒置時間;如果是 [Memory Limit Exceeded (超過記憶體限制)],表示處理程序超過單一處理程序記憶體的限制。
已使用的尖峰記憶體 (PeakMemoryUsed)處理程序使用過最大數量的記憶體。這個值對應到系統監視器中的 [Private Bytes (私用位元)] (最大量) 計數。

您可以找出程式碼,並在 Microsoft Visual Studio® .NET 或記事本中開啟另一個 Memory.aspx.cs 執行個體,來閱讀其附註。

每按一下 [Allocate 20 MB Objects (配置 20 MB 物件)] 按鈕,就會建立五個唯一的快取機碼和五個 20 MB 的物件,然後儲存在 System.Web 快取中。[Allocate 200 K Objects (配置 200 K 物件)] 按鈕也以相同的方式運作,但它會建立 200KB 的物件,然後再儲存在 System.Web 快取中。這個按鈕所代表的程式碼,會模擬一個非致命的遺漏狀況,以替代的測試案例進行實驗。

[Free Memory (釋放記憶體)] 可從 Session 範圍取得快取機碼清單,並清除快取。它也會呼叫 System.GC.Collect() 方法,強迫記憶體回收。

注意
   
使用 System.GC.Collect() 僅是示範說明,並不是建議的程序。明確呼叫 GC.Collect() 可變更 GC 自動調整的能力。重複呼叫 GC.Collect() 可在回收完成前,暫停所有執行緒。這可能會大幅降低效能。

最後,[Refresh Stats (重新整理統計資料)] 可重新整理記憶體統計資料。

您將使用 [工作管理員] 和 [系統監視器] 確認或發現更多關於 ASP.NET 處理程序的資訊:

若要使用工作管理員

  1. 先開啟 [工作管理員]。
  2. 在 [處理程序] 索引標籤上按一下 [檢視] 功能表,再按一下 [選擇欄位],然後選取 [虛擬記憶體大小] 核取方塊。

    這可將虛擬記憶體大小加入預設欄位。

    注意
       
    如果您是透過終端機伺服器用戶端使用 Windows 2000 Server,則要選取 [顯示來自所有使用者的處理程序] 核取方塊。

Aspnet_wp.exe 處理程序的虛擬記憶體大小是多少呢?在這個測試中,Aspnet_wp.exe 的虛擬記憶體大小是 9,820 KB。這個值是合理的參考基礎。

表 2.2:基礎工作管理員資料

處理程序虛擬記憶體大小
Aspnet_wp.exe9,820 KB

啟動 [系統監視器]。

若要啟動系統監視器

  1. 按一下控制台中的 [系統管理工具],然後按兩下 [效能]。
  2. 在 [系統監視器] 工具列,按一下 [+],然後從 [效能物件] 下拉式清單的 [新增計數器] 對話方塊中,選取 [.NET CLR 記憶體]。
  3. 在 [從清單選取例項] 方塊中按一下 [aspnet_wp]。
  4. 使用 CTRL + 按一下,從 [計數器] 清單方塊中選取 [圖 2.7] 的項目,然後按一下 [新增]。

    針對 [ASP.NET 應用程式] 和 [處理程序] 效能物件重複這個流程,並在選取適當的例項和計數器之後,按一下 [新增]。

  5. 按一下 [關閉],接著按一下工具列上的 [檢視報告] 按鈕,切換到文字檢視。您應該會看到一個類似 [圖 2.7] 的畫面。

ms954591.f02dbg07(zh-tw,MSDN.10).gif

[圖 2.7]:基礎系統監視器資料

使用系統監視器報告時,請特別注意 [# Bytes in all Heaps]、[Large Object Heap Size] 和 [Cache API Entries]。

  • [# Bytes in all Heaps] 顯示 [Gen 0 Heap Size]、[Gen 1 Heap Size]、[Gen 2 Heap Size] 和 [Large Object Heap Size] 計數器的總和。[# Bytes in all Heaps] 計數器,會以位元組為單位指示目前配置在記憶體回收堆積上的記憶體。
  • [Large Object Heap Size],以位元組為單位,顯示大型物件堆積目前的大小。請注意計數器是在記憶體回收結束時更新,不是在配置時更新。
  • [Cache API Entries] 是應用程式快取中的項目總數。

尚未配置記憶體之前的基礎值分別是:

  • Large Object Heap Size: 22,608 位元組
  • # Bytes in all Heaps: 2,589,252
  • Cache API Entries: 0
注意
   
若要深入了解系統監視器的 .NET 效能計數器,請參閱:

第一次配置操作

既然您已在執行 ASP.NET 處理程序的同時,建立好應用程式和系統監視器計數器的統計資料,現在請思考一下所耗用的原生和 Managed 記憶體數量。在 Memory.aspx 頁面上,按一下 [Allocate 20 MB Objects (配置 20 MB 物件)]。顯示在您瀏覽器上的資料應該類似下表:

表 2.3:基礎和配置瀏覽器資料

欄位基礎值新值
開始時間 (StartTime)07/08/2002 12:14:06 PM07/08/2002 12:14:06 PM
存留期 (Age)00:00:04.296177600:00:33:9585520
處理程序 ID (ProcessID)3,2123,212
要求計數 (RequestCount)01
狀態 (Status)資源存在 (Alive)資源存在 (Alive)
關閉原因 (ShutdownReason)N/AN/A
c5,9488,904

更新後的記憶體統計資料 (Updated Memory Stats)基礎值新值
GC.TotalMemory780 KB100,912 KB
Private Bytes8,896 KB117,616 KB

網頁上顯示的新表格中包含兩個項目:

  • [GC TotalMemory (GC 總記憶體)] 是 GC 堆積為 Managed 物件所配置的 Managed 記憶體總量。(這個數量對應到系統監視器 [.NET CLR Memory ] [Performance ] 物件的 [# Bytes in all Heaps ] 計數器)。
  • [Private Bytes (私用位元)] 是處理程序已配置出去、無法與其他處理程序共用的原生記憶體總大小。

在按一下 [Allocate 20 MB Objects (配置 20 MB 物件)] 之後,[RequestCount] 欄位會加 1,計算已完成的要求。您也可以發現 [GC TotalMemory ] 和 [Private Bytes ] 約等於 100 MB。

查看 ASP.NET 頁面的程式碼,瞭解 100 MB 的來源:

[Private Bytes (私用位元)] 圖表同時包含處理程序的 Managed 和原生配置。

範例案例在 ASP.NET 本身網頁上顯示 [Private Bytes (私用位元)] 和 [GC TotalMemory (GC 總記憶體)] 兩種記憶體統計資料。若在實際執行應用程式時進行偵錯,您可能需要使用其他工具,例如工作管理員和系統監視器以取得這項資訊。您也可以在系統監視器顯示 Managed 和原生記憶體資料時,使用工作管理員顯示原生資料。

在工作管理員的 [處理程序] 索引標籤中,找到有相同處理程序 ID (PID) 的 Aspnet_wp.exe 處理程序。您可以看到虛擬記憶體的大小增加到接近 115,000 KB。

表 2.4:基礎和一次配置工作管理員資料

處理程序基礎值新值
Aspnet_wp.exe9,820 KB114,840 KB

虛擬記憶體大小約增加了 100,000 KB。ASP.NET 處理程序用掉機器大部份的記憶體。

注意
   
工作管理員中的 [虛擬記憶體大小] 欄位,通常對應到系統監視器 [Process: Private Bytes ] 計數器。

在系統監視器的圖表檢視中,您可以看到 [Private Bytes (私用位元)] 和 [# Bytes in all Heaps ] 計數器值,以相同的速率增加。這表示是 Managed 記憶體的用量增加,而非原生記憶體。

[圖 2.8] 顯示按下按鈕時記憶體用量增加的幅度。若要變更圖中 Y 軸的最大值,請在圖中按右鍵,然後選取 [內容]。按一下 [圖表] 索引標籤,並在 [垂直刻度最大值] 方塊中變更數值。

ms954591.f02dbg08(zh-tw,MSDN.10).gif

[圖 2.8]:系統監視器圖表

第二次配置操作

接下來的步驟消耗更多的記憶體。再按一次 [Allocate 20 MB Objects (配置 20 MB 物件)],即按兩下。請注意 ASP.NET 網頁上通報的值如何變動,特別是 [RequestCount (要求計數)]、[PeakMemoryUsed (已使用的尖峰記憶體)] 和 [GC TotalMemory (GC 總記憶體)]。

表 2.5:基礎、一次配置和兩次配置瀏覽器資料

欄位基礎值一次配置兩次配置
開始時間 (StartTime)07/08/2002 12:14:06 PM07/08/2002 12:40:59 PM07/08/2002 13:25:51 PM
存留期 (Age)00:00:04.296177600:00:23:958552000:47:33.1343328
處理程序 ID (ProcessID)3,2123,2123,212
要求計數 (RequestCount)012
狀態 (Status)AliveAliveAlive
關閉原因 (ShutdownReason)N/AN/AN/A
已使用的尖峰記憶體 (PeakMemory Used)5,9488,904113,752

記憶體統計資料基礎值一次配置兩次配置
GC.TotalMemory780 KB100,912 KB200,916 KB
Private Bytes8,896 KB117,616 KB221,450 KB

若要看到第二次按按鈕事件對系統記憶體有何影響,請查看工作管理員中處理程序的細節。

表 2.6:基礎、一次配置和兩次配置工作管理員資料

處理程序基礎值一次配置 兩次配置
Aspnet_wp.exe9,820 KB114,840 KB216,240 KB

[VM Size] 值雖然再次增加了 100,000 KB,但 Aspnet_wp.exe 處理程序仍耗用掉大部份的記憶體。

系統監視器報告和圖表檢視確認這些值。現在來看一下 [Cache API Entries] 計數器。當您在看程式碼時,每個按一下事件都執行一次建立五個快取項目的迴圈。迴圈已執行兩次,因此有十個快取區 API 項目。

[# Bytes in all Heaps ] 和 [Large Object Heap Size ] 計數器之間的差異,表示大部份的 Managed 記憶體都正由大型物件堆積使用中。如 [圖 2.9] 所示。

ms954591.f02dbg09(zh-tw,MSDN.10).gif

[圖 2.9]:系統監視器資料

第三次配置操作

最後的步驟會消耗更多的記憶體。第三次按一下 [Allocate 20 MB Objects (配置 20 MB 物件)],快速切換至工作管理員的 [效能] 索引標籤,並檢視 [分頁檔使用記錄]。

注意
   
在 Windows XP 中,工作管理員] 在 [效能] 索引標籤上顯示的是 [分頁檔使用量],但在 Windows 2000 中,[工作管理員] 在 [效能] 索引標籤上顯示的是 [記憶體使用量]。

每按一次 [Allocate 20 MB Objects (配置 20 MB 物件)] 按鈕,圖表會顯示劇升的曲線,要到下回再按一次按鈕後,才會變平。如果回收處理程序,您會在 [分頁檔使用量] 中看到劇降的曲線。[圖 2.10] 顯示 [工作管理員] 中發生的這類情況。

ms954591.f02dbg10(zh-tw,MSDN.10).gif

[圖 2.10]:工作管理員資料

[工作管理員] 可用來「約略估算」整體系統效能。認可的記憶體配置給程式和作業系統,並且 [分頁檔使用記錄] 繪製出這些值。每發生一次按一下事件,您都可以看到圖表中的確認負載值成比例增加。

您也可以看到正在被回收的 Aspnet_wp.exe 處理程序的結果。如曾提及,記憶體使用量會隨著每按一次按鈕增加,要到處理程序回收後才會釋放。您可以查看應用程式事件記錄檔,確認處理程序是否已回收。

切換回瀏覽器中的 ASP.NET 網頁,並按一下 [Refresh Stats (重新整理統計資料)] 按鈕。現在您可以看到第二個表格加入到網頁,其中包含來自新處理程序的資料。因為達到記憶體限制,所以處理程序模型健康監控關閉原始處理程序,然後啟動了新的處理程序。比較這兩個表格,特別注意看 [Status (狀態)] 和 [ShutdownReason (關閉原因)] 欄位,以及 [Updated Memory (更新後的記憶體統計資料)] 表格。

第一處理程序的 [Status (狀態)] 欄位值已從 [Alive (資源存在)] 變更為 [Termindated (結束)],同時第二處理程序變為 [Alive (資源存在)]。因為超過記憶體限制,所以第一個處理程序已經結束。請注意,ASP.NET 是在您配置記憶體和超過最大記憶體尖峰的記憶體限制之後,才結束處理程序。

由於處理程序已回收,[Updated Memory Stats (更新後的記憶體統計資料)] 表又再度顯示基礎值。

深入探討

[Refresh Stats (重新整理統計資料)] 僅更新統計資料。若要改變結果,您可以利用瀏覽器介面上的控制項作實驗:

  • 再按一次 [Allocate 20 MB Objects (配置 20 MB 物件)],而不要按 [Refresh Stats (重新整理統計資料)]。第一個表格中的要求計數值遞增,而第二個表格顯示的要求計數為 0 (已建立新的工作處理程序)。當達到記憶體限制時,處理程序模型會使用智慧型轉送功能。它會確認要求、刪除舊的處理程序,並建立新的處理程序,以符合要求。
  • 再按一次 [Allocate 20 MB Objects (配置 20 MB 物件)]。由於我們正在處理新的工作處理程序,而且尚未達到那個處理程序的記憶體限制,所以這會累加第二個表格的要求計數值。
  • 重複地按 [Allocate 200 K Objects (配置 200 K 物件)]。這會建立多個 200 KB 的物件,最後累積成大型物件堆積。您可以試試看這個選項,它使用類似的程式碼路徑,但記憶體用量較少。
  • 讓我們檢視一下目前為止得到的結論:
  • 您正處理的是 Managed 記憶體用量問題。系統監視器圖表檢視顯示 [Private Bytes (私用位元)] 和 [# Bytes in all Heaps ] 計數器,以相等的幅度增加。
  • Aspnet_wp.exe 處理程序消耗掉大部份的記憶體。一旦 Aspnet_wp.exe 處理程序回收,認可的記憶體計數會回歸正常。
  • 大部份的 Managed 堆積記憶體用於大型物件堆積。大型物件堆積的大小,幾乎與 [Total # Bytes in all Heaps] 計數器的值相等。

利用 WinDbg 偵錯使用者模式傾印檔案

為了要確認這些發現的正確性,並瞭解更多有關 ASP.NET 處理程序中發生的狀況,可使用 ADPlus 建立一個傾印檔案,然後用 WinDbg 和 SOS.dll 檢視這個傾印檔案。如需 ADPlus 的詳細資訊,請參閱 Microsoft 知識庫文件編號 Q286350,《HOWTO: Use Autodump+ to Troubleshoot 'Hangs' and 'Crashes'》,網址為:http://support.microsoft.com/default.aspx?scid=kb;zh-tw;Q286350 (英文)。另外,如需透過終端伺服器執行 ADPlus 的詳細資訊,請參閱 Microsoft 知識庫文件編號 Q323478,《PRB: You Cannot Debug Through a Terminal Server Session》,網址為:http://support.microsoft.com/default.aspx?scid=kb;zh-tw;Q323478 (英文)。

若要從頭開始

  • 開啟命令提示字元視窗,然後在提示字元處輸入 iisreset,重新啟動 IIS。

依照預設,當記憶體耗用超過 60% 的可用 RAM 時,Aspnet_wp.exe 處理程序就會被回收。要到達記憶體限制所花的時間可能會縮短,因此只有很少的時間進行偵錯。為了要避免處理程序在您偵錯之前結束,應在處理程序被回收之前,修改登錄,以改變 ASP.NET 的預設行為。

加入登錄機碼

您需要加入兩個登錄值:DebugOnHighMemUnderDebugger。這些登錄設定通知 ASP.NET 要呼叫 DebugBreak 而不是結束處理序處理程序。

注意:下列練習需要您變更登錄。一般總是建議您在作變更之前先備份登錄。除此之外,您偵錯完之後,通常也會建議您刪除掉以下步驟所建立的 DWORD。這些登錄 DWORD 並未記載在文件中,所以應該只在特定的偵錯工作階段使用。

若要建立登錄值

  1. 啟動 Regedit,然後找到下列機碼:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET。
  2. 在 ASP.NET 上按右鍵,指向 [New (新增)],然後選取 [DWORD Value (DWORD 值)]。
  3. 將新的項目名稱從 [New Value # 1 (新增值 #1)] 變更為 [DebugOnHighMem]。
  4. 按兩下新的名稱,將 HEX 資料值從 0 變更為 1。
  5. 建立另一個 [New DWORD Value (新增一個新 DWORD 值)],並命名為 UnderDebugger。保留資料值為 HEX 0。

最後您的設定應該如 [圖 2.11]所示。

ms954591.f02dbg11(zh-tw,MSDN.10).gif

[圖 2.11]:登錄編輯器

每當偵錯工具附加至 Aspnet_wp.exe 處理程序之後,記憶體限制監控機制就會自動停用,除非 UnderDebugger DWOR 值設定為零,機制才不會停用。

設定 ADPlus 並建立傾印檔案

當 ADPlus 在 -crash 模式下執行時,若按 CTRL+C,會設定成建立一個完整傾印。開啟 ADPlus.vbs 指令碼,並將原先是 FALSE 的 Full_Dump_on_CONTRL_C 值,變更為 TRUE。下面是來自 ADPlus.vbs 的相關程式碼:

即使處理程序並未毀損,您仍可以在 -crash 模式下使用 ADPlus 收集處理程序資料。要擷取資訊,請再執行 ASP.NET 網頁一次。

在 -crash 模式下執行 ADPlus

  1. 瀏覽至 http://localhost/debugging/memory.aspx。
  2. 開啟命令提示字元,並從指令列啟動 ADPlus
  3. 指定 -crash 參數和處理程序名稱,這裡是 Aspnet_wp.exe

您也可以使用 -quiet 選項抑制額外的程式資訊,例如在這個範例中:

在這種情況下,ADPlus 輸出將在具有唯一名稱的子目錄 C:\ debuggers 中找到。若要指定輸出目錄,請使用 -o 參數指定路徑,如以下範例所示:

注意
   
-quiet 參數可確保在未設定 ADPlus 的符號路徑的情況下,仍能夠建立傾印檔案。

偵錯工具的輸出將類似以下所示:

您還應該看到一個執行 CDB.exe 的最小化視窗。

此時,您已建立好一個傾印檔案,因此可以看到是哪些程式正在消耗記憶體。

注意
   
ADPlus 所產生的傾印檔案可能會非常大。每按一下 [Allocate 20 MB Objects (配置 20 MB 物件)] 按鈕,就會配置 100 MB,因此您需要考慮一下電腦的可用磁碟空間。如果您有 512 MB 的 RAM,若連按三次按鈕,就需要 300+ MB 的可用磁碟空間。

若要建立傾印檔案,可使用 ASP.NET 網頁建立消耗記憶體的物件。

配置記憶體

  1. 切換至瀏覽器中的 Memory.aspx 網頁。
  2. 連按三次 [Allocate 20 MB Objects (配置 20 MB 物件)]。按了三次按鈕後,達到記憶體限制,此時 CDB.exe 建立傾印。
  3. 按一下 [Refresh Stats (重新整理統計資料)]。

偵錯工具結束處理程序、產生一個記錄檔,然後離開。找出 ADPlus 產生的檔案。如果您不在 ADPlus 指令列上指定路徑,這些檔案將放入以當機發生日期及時間所組成的目錄名稱下,如下列範例所示:

以下將描述ADPlus 所產生的典型檔案:

  • Autodump+-report.txt 是 ADPlus.vbs 建立的報告。它會顯示 ADPlus 所知道的常數清單,和常數目前的設定。如果 ADPlus.vbs 指令碼在經過修改後運作錯誤,這份報告可作為診斷之用。
  • PID-1234__process.exe.cfg 可傳遞給 CDB.exe,記錄需要執行的命令。這個檔案可用來診斷執行 ADPlus.vbs 時所發生的問題。
  • PID-1234__PROCESS.EXE__Reason__full/mini_timestamp.dmp 是處理程序的完整傾印檔案。PID 之後的數字是指傾印處理程序的處理程序 ID。Process.exe 將由處理程序的名稱取代;mini_timestamp 將由 timestamp 取代;而原因將由識別傾印原因的字串取代,例如用於當機傾印的「CTRL+C」。因此,典型的傾印檔名通常是 PID-344__ASPNET_WP.EXE__CTRL-C__full_2002-05-19_19-43-59-798_0158.dmp。這個範例檔案的大小是 358 MB。
  • PID-1234__PROCESS.EXE__Reason__full/mini_timestamp.log 是偵錯工具的輸出記錄檔。如果您要找尋當機前的例外狀況或事件記錄,這個檔案可能有所幫助。部份檔案名稱又再度由 PID、處理程序名稱和原因所取代。
  • Process_List.txt 是 Tlist.exe -v 的輸出檔。ADPlus.vbs 將使用這個檔案判斷正在機器上執行的處理程序的 PID 和名稱。

檢視傾印檔案

檢視回收 Aspnet_wp.exe 處理程序時所建立的傾印檔案。

使用 WinDbg 檢視傾印檔案

  1. 在 [開始] 功能表上,指向 [Debugging Tools for Windows],然後按一下 [WinDbg]。
  2. 在 [File (檔案)] 功能表上,按一下 [Open Crash Dump (開啟當機傾印)]。
  3. 選取適當的傾印檔案,然後按一下 [Open (開啟)]。
  4. 如果您看到提示要求您 [Save Base Workspace Information (儲存基底工作區資訊)],請按一下 [No (否)]。
  5. 如果反組譯碼視窗出現,請關閉該視窗,然後在 [Window (視窗)] 功能表上按一下 [Automatically Open Disassembly (自動開啟反組譯碼)]。

您必須輸入偵錯工具所用模組的符號路徑,才能分析傾印檔案。偵錯電腦上所需的符號檔案版本,應該與產生傾印檔案的系統版本相同。在這個案例中,您需要加入 .NET Framework SDK、Visual C Runtime、和 Windows 的符號。

若要輸入符號路徑,請執行下列其中一項作業:

  • 在指令列上,輸入:
  • 透過 _NT_SYMBOL_PATH 環境變數,輸入 WinDbg 的符號路徑。
  • 在 WinDbg 的 [File (檔案)] 功能表上,選取 [Symbol File Path (符號檔案路徑)],然後輸入:

路徑中的「SRV」通知 WinDbg 移至外部符號伺服器,並將符號複製到本機符號快取內。

若要將這些相同的符號路徑用於其他傾印

  • 在 [File (檔案)] 功能表上,選取 [Save Workspace As (另存新工作區)],然後輸入已儲存路徑的名稱,例如 [.NET Debugging Symbols (.NET 偵錯符號)]。

WinDbg 可偵錯機器碼。若要檢視 Managed 程式碼,您需要載入 SOS.dll 偵錯工具擴充程式。

注意
   
如果您尚未安裝 SOS.dll 偵錯工具擴充程式,請現在安裝。如需下載的指示,請參閱第 1 章〈介紹.NET Framework 應用程式的實際執行環境偵錯〉。

若要檢視 Managed 程式碼

  1. 按下 ALT+1 開啟 WinDbg 命令視窗。
  2. 輸入 .load <SOS>\SOS.dll 以載入擴充程式,然後以 SOS.dll 位置取代 <SOS>。
  3. 使用 !findtable 命令初始化 SOS 以及即將偵錯的 Runtime 版本的表格資訊。

輸出應該類似以下所示:

注意
   
SOS.dll 需要與 .NET Runtime 版本相同的 .bin 檔案。在先前範例中,想要找到正確的版本,必須先檢視兩個 .bin 檔案。未來 SOS 將不需要 .bin 檔案。

由於您將 ADPlus 設定成要建立完整傾印,而且這個傾印檔名利用 CTRL+C 的參考產生,所以您會知道是否呼叫過 DebugBreak,以及處理程序模型的健康監控功能是否偵測到記憶體耗用過度。

ADPlus 在 Aspnet_wp.exe 處理程序被回收時建立傾印。如您在應用程式事件記錄檔項目中所見,耗用掉的記憶體超過 60% 的可用 RAM。若要解決這個記憶體耗用問題,您需要檢視傾印檔案,而且這牽涉到某些並非可以依直覺執行的步驟。下文將說明這些步驟。

分析傾印:摘要

在研究細節之前,這裡提供您在分析傾印檔案時應遵循的步驟摘要。

在處理記憶體耗用問題時,應先考慮 GC 正在做什麼動作。您可以使用 !eeheap WinDbg 命令列出 Managed 堆積大小,然後比較這個大小與傾印檔案的總大小。

接下來找出 .NET Managed 物件在 Managed 堆積中所佔用的空間。在這個範例中,可使用 !dumpheap 命令找出在大型物件堆積中有多少 System.Byte [] 物件。

您需要知道這些物件將存在多久以及他們目前的大小。 !gcroot 命令顯示 System.Byte [] 物件具有強式參考,這表示他們已生根。System.Web.Caching.Cache 類別會參考 System.Byte [] 物件,這也是為何這些物件會生根的原因。

現在您已經找到 ASP.NET 快取所參考的物件,因此可嘗試找出該快取所參考的記憶體用量。

若要搜尋 System.Web.Caching.Cache 的記憶體位址,應使用具有組件和類別名稱的 !name2ee 命令。這會傳回 MethodTable 位址,而且您可以使用 !dumpheap 命令,針對有 MethodTable 的物件,傾印 Managed 堆積內容。

!dumpheap 命令可提供確實的 System.Web.Caching.Cache 物件位址,並可以與 !objsize 命令共用,尋找根在 GC 堆積上存在的位元組數。由於這個大小極接近傾印檔案的大小,因此可以推論出 System.Web.Caching.Cache 物件參考到大部份的 Managed 記憶體。

分析傾印:細節

首先考慮 GC 堆積的大小,然後將它與傾印檔案總大小做比較。

使用 !eeheap 命令列出 GC 堆積大小、層代 0、1、2 的起始位址以及大型物件堆積。然後您可以將 GC 堆積大小和傾印檔案的總大小做比較。

利用 !eeheap -gc 檢視 GC 堆積的大小:

在本例中,GC 堆積大小超過 300 MB,而且整個傾印大小是 358 MB。此時可以使用 !dumpheap -stat 命令找出 .NET Managed 物件在 GC 堆積中所佔的空間。這個命令產生的輸出分為四欄,其標題各為 MTCountTotalSizeClass NameMT 輸出代表方法表格,可辨識物件的類型,排列順序是根據總大小,由小而大列出。TotalSize 代表物件大小乘以物件數量的結果。

注意
   
由於必須回收和顯示的資料量很大,所以 !dumpheap–stat 命令可能要花很長的時間執行。

以下為 !dumpheap–stat 命令所列出的 14,459 項物件中的前面幾項。

[dumpheap -stat] 清單的結尾顯示在第二張清單的下方。具有最大[TotalSize (總大小)] 項目的物件,位在 MT 清單的尾端,其中包括有 [System.String] 、[System.Byte] [] 和 [System.Object] []:

System.String 位於清單的最後一個物件,因為它的 TotalSize 最大。Free 項目代表未受到參考的物件,而且記憶體已回收,但該記憶體尚未壓縮且未釋放回作業系統。

這個資料之後緊接著是大型物件堆積的物件清單,包括所有超過 85 KB 的物件。

您可以使用 !gcroot 命令,瞭解更多有關大型物件堆積的物件,這個命令可找到物件的根。您可以使用 dumpheap -stat 輸出的位址,來指定物件。例如,在前一個表列清單中,!gcroot 17b90018 提供第一個 System.Byte [] 物件的資訊。下面是 !gcroot 的輸出:

掃描執行緒行顯示 !gcroot 命令正在掃描物件根參考的執行緒。掃描 HandleTable 行顯示命令也在掃描根的 HandleTables。每個應用程式定義域 (或 AppDomain),是執行應用程式的獨立環境,都具有一個 HandleTable,而且 HandleTable 只是根參考的另一個來源。執行緒找到的參考可能位在登錄、引數或區域變數中。

如果輸出包含 HANDLE (Strong),則找到強式參考。這表示物件已生根,而且記憶體無法回收。附錄中還列有其他參考類型。

這個傾印顯示類似 !gcroot 掃描的 System.Byte [] 物件資料。嘗試從 dumpheap -stat 的輸出傳送另一個位址到 !gcroot。該輸出除了以反白顯示 System.Web.Caching.CacheEntry 的位址以外,大致上與先前 System.Byte [] 物件相同。其他類別則指示回收。

知道快取中參考到多少記憶體用量是很有用的。若要搜尋 System.Web.Caching.Cache 的記憶體位址,應使用 !name2ee 命令。這個命令用到兩個參數—組件名稱和完整類別名稱,System.Web.Caching.Cache。如果您不知道組件名稱,可以到 .NET Framework SDK 文件中找一找。注意,您也可以使用 System.Web.Caching.CacheSingle 取得類似的資料。

注意
   
EEClass 是用來表示 .NET 類別的內部結構。若要瞭解更多有關 SOS 命令的輸出,請參閱 SOS 說明。

若要傾印這類型物件的 Managed 堆積內容,應使用 !dumpheap 命令傳送 MethodTable 位址。例如:

下面是預期中將看到的 !dumpheap 的輸出:

由於不需要指定 MethodTable 的資訊,所以使用 -mt 參數可能會令人覺得有點疑惑;但是,當物件位址傳送至 !dumpheap -mt 時,AppDomain 中所有正在執行的這類型物件都會被列出來。如果有多個 AppDomains,這裡將列出每個物件的執行個體。

您可以使用這個資訊,將相關的位址傳送至 !objsize 命令,以取得每個物件的大小。在本例中,只有一個位址,因此可使用命令 !objsize 0108b8ac 傾印 System.Web.Caching.Cache 的大小。下面是預期中將看到的 !objsize 的輸出:

注意
   
執行 !objsize 命令時若未附加任何參數,則將列出處理程序中所有的根。

如需 ASP.NET 中快取的詳細資訊,請參閱 GotDotNet 網站上的〈ASP.NET Performance Tips and Best Practices〉,網址為:http://code.msdn.microsoft.com/default.aspx?SiteEntry=gdn (英文)。

釋放 Managed 記憶體

釋放 Managed 記憶體並考慮虛擬記憶體上的效果。

若要確定所有相關程式都已重新啟動

  1. 開啟命令提示字元視窗,然後在提示字元處輸入 iisreset,重新啟動 IIS。
  2. 瀏覽至 http://localhost/debugging/memory.aspx。
  3. 執行 [系統監視器] 並確定以下計數器都設定好:Aspnet_wp.exe 的 [Private Bytes (私用位元)] 和 [# Bytes in all Heaps]。
  4. 按一下 [Allocate 20 MB Objects (配置 20 MB 物件)]。您將看到類似 [圖 2.8] 的輸出。

    請注意系統監視器顯示的變更。Aspnet_wp.exe 的 [Private Bytes (私用位元)] 和 [# Bytes in all Heaps] 計數器都成比例增加。

  5. 再按一次 [Allocate 20 MB Objects (配置 20 MB 物件)],注意系統監視器圖表的改變。

    每按一次按鈕,[Private Bytes (私用位元)] 和 [# Bytes in all Heaps] 計數器都成比例增加。

[圖 2.12] 顯示按兩次 [Allocate 20 MB Objects (配置 20 MB 物件)] 之後系統監視器的結果。如果您切換回瀏覽器並按一下 [Free Memory (釋放記憶體)],即便是已按過按鈕,您仍可以看到系統監視器記錄檔顯示 [Maximum private bytes] 是 222 MB。

ms954591.f02dbg12(zh-tw,MSDN.10).gif

[圖 2.12]:配置過記憶體之後的系統監視器資料

下面是從 [Free Memory (釋放記憶體)] 按鈕所代表的程式碼中擷取出來的內容:

這個程式碼還包含對 [GC.Collect()] 的呼叫。這個程式碼清除了快取物件並導致記憶體回收,但是它不會使大型物件堆積釋放掉所配置的虛擬記憶體。[圖 2.13] 顯示快取正在清空而且 [GC.TotalMemory] 的記憶體用量也在縮減,但是 [Private Bytes (私用位元)] 的用量仍偏高。

ms954591.f02dbg13(zh-tw,MSDN.10).gif

[圖 2.13]: 按一下釋放記憶體之後的瀏覽器資料

按一下 [Free Memory (釋放記憶體)] 按鈕後,幾乎只釋放 1 MB 的記憶體。[# Bytes in all Heaps] 計數器大幅減少,但是 [Private Bytes (私用位元)] 計數器由於大型物件堆積的緣故,所以沒有變動。如 [圖 2.14] 所示。

ms954591.f02dbg14(zh-tw,MSDN.10).gif

[圖 2.14]:按一下 [Free Memory (釋放記憶體)] 之後的系統監視器圖表

擷取傾印

若要瞭解有關處理程序狀態的詳細資訊,請在 -hang 模式下執行 ADPlus,以擷取處理程序狀態的傾印:

啟動 WinDbg 並載入傾印檔案、設定符號路徑,並載入及初始化 SOS 偵錯工具擴充程式。現在請使用 !eeheap -gc 命令尋找 Managed 堆積的大小。

傾印顯示 GC 堆積大小是 1 MB 而不是 222 MB,表示只回收 1 MB。由於所配置的物件是 20 MB,因此可得出結論所有物件都已從堆積中移除。Aspnet_wp 的 [Private Bytes (私用位元)] 計數器仍是 222 MB,因此,您需要對這個數據做進一步的研究。

在偵錯工具中,使用 !dumpheap 命令和「-stat」旗標。其輸出應該類似以下所示:

總之,ASP.NET 網頁配置了 20 MB 的大型物件,然後將他們快取到 ASP.NET 的快取記憶體中。由於所配置的物件超過 85 KB,因此他們會配置到大型物件堆積上。當物件從程式碼的快取中移除之後,Managed 記憶體 (由系統監視器的 [# Bytes in all Heaps] 計數器顯示) 從 200 MB 減少到 1 MB。但是,處理程序的私用位元組不會減少,維持在大約 222 MB。雖然 Managed 記憶體已釋放,但是 VirtualAlloc API 所配置的記憶體仍會配置給處理程序。

這是 1.0 版大型物件堆積的已知限制。大型物件堆積會逐漸增加,以容納曾經存在的所有物件的記憶體。如果物件獲得釋放,該記憶體可供新的大型配置重複使用;但是處理程序的私用位元組會在物件釋放後壓縮。這個功能已加入 .NET Framework 1.1 版。

到目前為止,本章闡述了:

  • Managed 程式碼是如何耗用掉過多的記憶體。
  • ASP.NET 處理程序回收是如何發生及發生的時間。
  • 用來偵錯記憶體耗用問題的技術。
  • 如何在偵錯處理程序期間使用類似以下的工具:系統監視器、工作管理員、WinDbg 和 SOS.dll。

利用 Allocation Profiler 診斷記憶體遺漏

「Allocation Profiler」是另一種工具,可提供更多有關記憶體耗用的看法。當應用程式執行時,這個工具可檢視 Managed 堆積,並以圖形顯示堆積的記憶體配置。在本章節中,這個工具將顯示當超過 85 KB 的物件配置在大型物件堆積上時,會出現什麼樣的結果。Allocation Profiler 顯示最大物件生根的位置,以及對應的配置記憶體用量。

使用具有 ASP.NET 程式碼的 Allocation Profiler 之前,您需要修改 machine.config 檔案並將 <processModel> 項目中的使用者名稱屬性從 machine 變更為 system ,以啟用您使用工具的權限。當您使用完工具之後,應將使用者名稱屬性變更回 machine,避免使執行該工具相關的系統帳戶有安全上的顧慮。

若要執行 Allocation Profiler

  1. 從 C:\Debuggers\AllocProf 路徑執行 Allocationprofiler.exe。
  2. 從 [File (檔案)] 功能表選取 [Profile ASP.NET (設定檔 ASP.NET)]。

    多個資訊命令視窗出現。其中一個視窗指示工具正要停止 IIS Admin 服務,而另一個視窗指示正在啟動全球資訊網伺服器。Allocation Profiler 視窗標題變更為「Stopping IIS (即將停止 IIS)」。然後 IIS 重新啟動,這時視窗標題變更為「Waiting for ASP.NET worker process to start up (正在等候 ASP.NET 工作處理程序啟動)」。這意謂工具正在等候第一個 ASP.NET 網頁叫用 Aspnet_wp.exe。

  3. 瀏覽至 http://localhost/debugging/memory.aspx 以啟動 ASP.NET 工作處理程序。Allocation Profiler 視窗標題變更為「Profiling: ASP.NET (設定檔:ASP.NET)」。
  4. 清除 [Profiling active (設定檔作用中)] 核取方塊,關閉 [Allocation Graph for: ASP.NET (配置圖表用於:ASP.NET)] 視窗,然後勾選 [Profiling active (設定檔作用中)] 核取方塊。

    現在 Allocation Profiler 開始收集與處理程序有關的配置統計資料。

  5. 切換至瀏覽器視窗,並按兩次 [Allocate 20 MB Objects (配置 20 MB 物件)] 按鈕。
  6. 切換至 Allocation Profiler,然後清除 [Profiling active (設定檔作用中)] 核取方塊。
  7. 向右滑動水平捲軸,捲動過配置。

ms954591.f02dbg15(zh-tw,MSDN.10).gif

[圖 2.15]:配置圖表

畫面顯示 Button::OnClick 處理常式呼叫 Memory::btn20MB_Click 方法,建立了 191 MB 的 System.Byte 陣列物件。

若要檢視堆積圖表

  1. 關閉 [配置圖表:ASP.NET] 視窗。這是 ASP.NET 的配置圖表。
  2. 切換至 Allocation Profilerr 視窗,然後按一下 [Show Heap Now (立即顯示堆積)]。名為「Heap Graph for ASP.NET (ASP.NET 的堆積圖表)」的新 Allocation Profiler 視窗出現。
  3. 捲動至視窗的最右側,您將看到 System.Byte [] 物件的配置。

ms954591.f02dbg16(zh-tw,MSDN.10).gif

[圖 2.16]:堆積圖表

倒數第二個矩形圖顯示 System.Byte [] 物件生根於 System.Web 快取中。這個圖中顯示 Caching.CacheSingle 和 Caching.CacheEntry 參考樹狀結構中的參考。請注意 Caching.CacheEntry 受到呼叫,並包含 191 MB。這表示大部份記憶體由快取佔用。

若要檢視長條圖

  1. 關閉圖表視窗。
  2. 在 Allocation Profiler 視窗中,按一下 [View (檢視)] 功能表上的 [Histogram by Age (存留期的長條圖)]。

    ms954591.f02dbg17(zh-tw,MSDN.10).gif

    [圖 2.17]:存留期的長條圖

  3. 在現存物件存留期長條圖視窗的左窗格中,向右捲動,直到您看到大型紅色區塊。

    System.Byte [] 物件位在現存物件佔用的記憶體總量清單的頂端。

  4. 將滑鼠箭頭放置在紅色區塊上,可取得更多的資訊。
  5. 在代表System.Byte [] 的紅色區塊上按右鍵,然後按一下 [Context (內容)] 功能表上的 [Show Who Allocated (顯示配置對象)]。

    原先的 [Allocation graph for ASP.NET (配置圖表用於 ASP.NET)] 顯示。

Allocation Profiler 也可以追蹤根樹狀結構、顯示呼叫端、被呼叫端、以及哪一個呼叫配置哪一塊記憶體區塊。

若要顯示根樹狀結構資訊

  1. 在物件配置圖表視窗中,在 [Memory: btn20MB_Click (記憶體:btn20MB_Click)] 列上按右鍵,然後選取呼叫端和被呼叫者。這會使整個呼叫樹狀結構反白顯示。
  2. 在反白顯示區域上按右鍵,按一下 [Copy as text to clipboard (以文字方式複製到剪貼簿)],然後將文字送入記事本。

會顯示下列資訊:

這個樹狀結構顯示來自 btn20MB_Click 的呼叫是如何建立大型物件。

當您結束執行 Allocation Profiler 的時候,按一下 [Kill ASP.NET (刪除 ASP.NET)]。將 machine.config 中 <processModel> 項目上的使用者名稱屬性從 system 變更回 machine,再度限制權限,避免發生在 system 下執行程式所造成的安全性風險。

Allocation Profiler 檢視 Managed 堆積,並顯示 btn20MB_Click 事件所建立的 20 MB 大型物件。實際的程式碼從 btn20MB_Click 呼叫 CreateLargeObject,因而產生 System.Byte []。在發行版本中,CreateLargeObject 由於最佳化或內嵌而不會出現。這將在第 3 章〈偵錯爭用問題〉中加以討論。偵錯版本將會顯示 btn20MB_ClickCreateLargeObject 的呼叫。從兩種版本中,您可以發現大型物件已生根固定住,(所以 GC 無法回收),並且相當大量的記憶體已配置給大型物件。

結論

在這個章節中,您可以:

  • 知道如何使用 Microsoft 工具診斷及偵錯記憶體配置問題。
  • 學習到物件是如何配置在 Managed 堆積上。
  • 學習到 .NET 記憶體回收是如何運作。

工作管理員和系統監視器讓您收集有助於診斷記憶體遺漏的證據。WinDbg,連同 SOS 擴充功能,允許您精確地找到 Managed 記憶體配置在應用程式中的位置。Allocation Profiler 可用來顯示 Managed 記憶體是如何配置,並可以用來尋找物件根。

最後,您應該瞭解到,在 ASP.NET 應用程式中使用大於 85 KB 的物件之前,要仔細地考慮,因為在 .NET Framework 1.0 版大型物件堆積中的記憶體,GC 是無法回收的。

Page view tracker