共用方式為


記憶體回收和效能

本主題描述與記憶體回收和記憶體使用量相關的議題。 其中將解決與 Managed 堆積相關的議題,並且說明如何將記憶體回收對應用程式的影響降至最低。 每個議題都有程序連結,可讓您用來調查問題。

此主題包括下列章節:

  • 效能分析工具

  • 疑難排解效能問題

  • 疑難排解方針

  • 效能檢查程序

效能分析工具

下列章節描述可用於調查記憶體使用量和記憶體回收問題的工具。 本主題稍後提供的程序就是指這些工具。

記憶體效能計數器

您可以使用效能計數器收集效能資料。 如需相關指示,請參閱執行階段分析。 效能計數器的 .NET CLR Memory 分類提供了記憶體回收行程的相關資訊,如記憶體效能計數器中所述。

具有 SOS 的偵錯工具

您可以使用具有 SOS.dll (SOS 偵錯擴充功能)Windows 偵錯工具 (WinDbg) (英文) 或 Visual Studio 偵錯工具檢查 Managed 堆積上的物件。

若要安裝 WinDbg,請從 WDK 和開發人員工具網站 (英文) 安裝 Debugging Tools for Windows。 如需使用 SOS 偵錯擴充功能的詳細資訊,請參閱 HOW TO:使用 SOS

記憶體回收 ETW 事件

Windows 事件追蹤 (ETW) 是一套追蹤系統,可補充 .NET Framework 所提供的程式碼剖析和偵錯支援。 從 .NET Framework 4 版開始,記憶體回收 ETW 事件會擷取有用的資訊,從統計的觀點來分析 Managed 堆積。 例如,在記憶體回收即將發生時引發的 GCStart_V1 事件會提供下列資訊:

  • 要回收的物件層代。

  • 觸發記憶體回收的項目。

  • 記憶體回收的類型 (並行或非並行)。

ETW 事件記錄很有效率,不會遮蔽任何與記憶體回收相關的效能問題。 處理序可以將自己的事件與 ETW 事件一起提供。 記錄後,應用程式的事件和記憶體回收事件可以相互關聯,以判斷堆積問題發生的原因和時間。 例如,伺服器應用程式可以在用戶端要求的開頭和結尾提供事件。

分析 API

Common Language Runtime (CLR) 程式碼剖析介面會提供有關記憶體回收期間受影響之物件的詳細資訊。 分析工具可以在記憶體回收開始和結束時收到通知。 它可以提供有關 Managed 堆積上物件的報告,包括每個層代內物件的識別。 如需詳細資訊,請參閱分析 API 的物件追蹤

分析工具可以提供各方面的資訊。 不過,複雜的分析工具可能會修改應用程式的行為。

應用程式定義域資源監視

從 .NET Framework 4 開始,應用程式定義域資源監控 (ARM) 可讓主機藉由應用程式定義域監控 CPU 和記憶體使用量。 如需詳細資訊,請參閱應用程式定義域資源監視

回到頁首

疑難排解效能問題

第一個步驟是判斷是否真的是記憶體回收的問題。 如果判斷出是記憶體回收的問題,請從下列清單選取要疑難排解的問題。

  • 擲回記憶體不足例外狀況

  • 處理序使用太多記憶體

  • 記憶體回收行程回收物件的速度不夠快

  • Managed 堆積太分散

  • 記憶體回收暫停的時間太長

  • 層代 0 太大

  • 記憶體回收期間的 CPU 使用量太高

問題:擲回記憶體不足例外狀況

有兩種情況符合擲回 Managed OutOfMemoryException 的條件:

  • 虛擬記憶體不足。

    記憶體回收行程以預定大小的區段從系統配置記憶體。 如果配置需要額外的區段,但是處理序的虛擬記憶體空間中已沒有連續的可用區塊,則 Managed 堆積的配置將會失敗。

  • 沒有足夠的實體記憶體可供配置。

效能檢查

判斷記憶體不足例外狀況是否為 Managed。

判斷可以保留多少虛擬記憶體。

判斷實體記憶體是否足夠。

如果您判斷出例外狀況並不合法,請連絡 Microsoft 客戶服務及支援中心並提供下列資訊:

  • 包含 Managed 記憶體不足例外狀況的堆疊。

  • 完整的記憶體傾印。

  • 證明記憶體不足例外狀況不合法的資料,包括顯示虛擬或實體記憶體並非問題所在的資料。

問題:處理序使用太多記憶體

一般會假設 Windows 工作管理員的 [效能] 索引標籤上的記憶體使用量顯示器會指出使用太多記憶體的情況。 不過,該顯示器與工作集相關,不會提供有關虛擬記憶體使用量的資訊。

如果您判斷出是 Managed 堆積造成問題發生,則必須測量 Managed 堆積一段時間,以判斷是否有任何模式。

如果您判斷出不是 Managed 堆積造成問題發生,則必須使用機器碼偵錯。

效能檢查

判斷可以保留多少虛擬記憶體。

判斷 Managed 堆積認可的記憶體數目。

判斷 Managed 堆積保留的記憶體數目。

判斷層代 2 中的大型物件。

判斷物件的參考。

問題:記憶體回收行程回收物件的速度不夠快

當記憶體回收的物件回收似乎不如預期時,您必須判斷這些物件是否有任何強式參考。

如果包含無作用物件的層代並沒有記憶體回收,也可能遇到此問題,這表示無作用物件的完成項並未執行。 例如,當您執行單一執行緒 Apartment (STA) 應用程式,而為完成項佇列提供服務的執行緒無法對其內部進行呼叫時,就可能發生此問題。

效能檢查

檢查物件的參考。

判斷完成項是否已經執行。

判斷是否有等待完成的物件。

問題:Managed 堆積太分散

分散程度的計算方式是層代總共配置的記憶體中,可用空間所佔的比例。 對於層代 2 來說,可接受的分散程度不可超過 20%。 因為層代 2 可能變得相當大,因此分散的比例比絕對值更重要。

在層代 0 中有許多可用空間並不成問題,因為這是配置新物件所在的層代。

分散永遠發生在大型物件堆積中,因為它不會壓縮。 相鄰的可用物件自然會收合為單一空間,以滿足大型物件配置的要求。

分散可能在層代 1 和層代 2 中演變成問題。 如果這些層代在記憶體回收之後擁有大量可用空間,則應用程式的物件使用量可能需要修改,而且您應該考慮重新評估長期存在之物件的存留期。

固定過多物件可能會提高分散程度。 如果分散程度太大,可能會固定太多物件。

如果虛擬記憶體分散造成記憶體回收行程無法加入區段,可能是下列其中一項原因所造成:

  • 經常載入與卸載許多小組件。

  • 與 Unmanaged 程式碼互通時保留太多 COM 物件的參考。

  • 建立大量暫時性物件,這樣會造成大型物件堆積經常配置和釋出堆積區段。

    裝載 CLR 時,應用程式可以要求記憶體回收行程保留其區段。 這樣會降低區段配置的頻率。 這個做法可藉由在 STARTUP_FLAGS 列舉 中使用 STARTUP_HOARD_GC_VM 旗標的方式達成。

效能檢查

判斷 Managed 堆積中的可用空間量。

判斷已固定的物件數目。

如果您認為分散並沒有不合法的原因,請連絡 Microsoft 客戶服務及支援中心。

問題:記憶體回收暫停的時間太長

記憶體回收會以非強制的方式即時執行,因此應用程式必須能夠接受暫停時間。 非強制即時執行的原則為,95% 的作業必須準時完成。

進行並行記憶體回收時,Managed 執行緒可以在回收期間執行,這表示暫停時間非常短暫。

暫時的記憶體回收 (層代 0 和 1) 只會持續幾毫秒,因此通常無法縮短暫停時間。 不過,您可以藉由變更應用程式配置要求的模式,縮短層代 2 中的暫停時間。

另一種更精確的方法是使用記憶體回收 ETW 事件。 您可以藉由在事件序列中加入時間戳記差異的方式,找出回收的時機。 整個回收序列包含執行引擎暫止、記憶體回收本身,以及繼續執行執行引擎。

您可以使用記憶體回收告知判斷伺服器是否即將擁有層代 2 回收,以及重新路由至另一部伺服器的要求是否能夠解決任何有關暫停的問題。

效能檢查

判斷記憶體回收時間的長度。

判斷造成記憶體回收的原因。

問題:層代 0 太大

層代 0 可能在 64 位元系統上擁有大量物件,尤其是您使用伺服器記憶體回收,而非工作站記憶體回收時。 這是因為在這類環境中觸發層代 0 記憶體回收的臨界值較高,而且層代 0 可能變得相當大。 如果應用程式在觸發記憶體回收之前配置更多記憶體,就能改善效能。

問題:記憶體回收期間的 CPU 使用量太高

記憶體回收期間的 CPU 使用量將會很高。 如果處理記憶體回收耗費了相當長的時間,表示回收數目太過頻繁,或是回收過程太長。 Managed 堆積上物件的配置率增加,就會造成記憶體回收發生的頻率提高。 降低配置率就能降低記憶體回收的頻率。

您可以使用 Allocated Bytes/second 效能計數器來監控配置率。 如需詳細資訊,請參閱記憶體效能計數器

回收的持續時間主要取決於配置後剩餘的物件數目。 如果剩下很多要回收的物件,記憶體回收行程就必須處理大量記憶體。 壓縮剩餘物件的工作相當費時。 若要判斷回收期間處理的物件數目,請在偵錯工具中針對指定的層代於記憶體回收結束位置設定一個中斷點。

效能檢查

判斷記憶體回收是否造成高 CPU 使用量。

在記憶體回收的結束位置設定中斷點。

回到頁首

疑難排解方針

本節描述當您開始調查時,應考慮的方針。

工作站或伺服器記憶體回收

判斷您使用的記憶體回收類型是否正確。 如果您的應用程式使用多個執行緒和物件執行個體,請使用伺服器記憶體回收,而不要使用工作站記憶體回收。 伺服器記憶體回收會在多個執行緒上運作,而工作站記憶體回收則需要多個應用程式執行個體執行自己的記憶體回收執行緒並且爭用 CPU 時間。

如果是負載較低且不常在背景中執行工作的應用程式 (例如服務),則可以使用工作站記憶體回收,並且停用並行記憶體回收。

測量 Managed 堆積大小的時機

除非您使用分析工具,否則必須建立一致的測量模式,才能有效診斷效能問題。 建立排程時,請考慮下列幾點:

  • 如果您在層代 2 記憶體回收之後進行測量,則整個 Managed 堆積將不會有任何無作用物件。

  • 如果您在層代 0 記憶體回收之後立即進行測量,則還不會回收層代 1 和層代 2 中的物件。

  • 如果您在記憶體回收之前進行測量,則會測量到記憶體回收開始之前的最大配置量。

  • 在記憶體回收期間進行測量會造成許多問題,因為記憶體回收行程資料結構並非處於可周遊的有效狀態,因此可能無法提供完整的結果。 這是設計上的預期行為。

  • 如果您使用工作站記憶體回收同時啟用並行記憶體回收,則不會壓縮回收的物件,因此堆積大小可能相同或更大 (分散可能讓它看起來更大)。

  • 實體記憶體負載過高時,層代 2 上的並行記憶體回收就會延遲。

下列程序描述如何設定中斷點,以便測量 Managed 堆積。

若要在記憶體回收的結束位置設定中斷點

  • 在載入 SOS 偵錯工具擴充功能的 WinDbg 中,輸入下列命令:

    bp mscorwks!WKS::GCHeap::RestartEE "j (dwo(mscorwks!WKS::GCHeap::GcCondemnedGeneration)==2) 'kb';'g'"

    其中 GcCondemnedGeneration 會設定為所需的層代。 這個命令需要專用符號。

    如果 RestartEE 是在記憶體回收完成層代 2 的物件回收之後執行,則這個命令會強制進行中斷。

    在伺服器記憶體回收中,只有一個執行緒會呼叫 RestartEE,因此中斷點只會在層代 2 記憶體回收過程中發生一次。

回到頁首

效能檢查程序

本節描述下列區分效能問題之原因的程序:

  • 判斷問題是否為記憶體回收所造成。

  • 判斷記憶體不足例外狀況是否為 Managed。

  • 判斷可以保留多少虛擬記憶體。

  • 判斷實體記憶體是否足夠。

  • 判斷 Managed 堆積認可的記憶體數目。

  • 判斷 Managed 堆積保留的記憶體數目。

  • 判斷層代 2 中的大型物件。

  • 判斷物件的參考。

  • 判斷完成項是否已經執行。

  • 判斷是否有等待完成的物件。

  • 判斷 Managed 堆積中的可用空間量。

  • 判斷已固定的物件數目。

  • 判斷記憶體回收時間的長度。

  • 判斷觸發記憶體回收的原因。

  • 判斷記憶體回收是否造成高 CPU 使用量。

若要判斷問題是否為記憶體回收所造成

  • 檢查下面這兩個記憶體效能計數器:

    • % Time in GC。 顯示自上次記憶體回收循環之後,執行記憶體回收所耗用的時間百分比。 使用這個計數器可判斷記憶體回收行程是否耗用太多時間提供 Managed 堆積空間。 如果記憶體回收所耗用的時間相對短,可能表示 Managed 堆積外部發生資源問題。 若涉及並行或背景記憶體回收,則這個計數器可能不正確。

    • # Total committed Bytes。 顯示記憶體回收行程目前認可的虛擬記憶體數目。 使用這個計數器可判斷記憶體回收行程所消耗的記憶體,是否佔應用程式所使用記憶體的相當大部分。

    大部分記憶體效能計數器會在每次記憶體回收結束時更新。 因此,這些計數器可能不會反映您想獲得相關資訊的目前狀況。

若要判斷記憶體不足例外狀況是否為 Managed

  1. 在載入 SOS 偵錯工具擴充功能的 WinDbg 或 Visual Studio 偵錯工具中,輸入列印例外狀況 (pe) 命令:

    !pe

    如果例外狀況為 Managed,則 OutOfMemoryException 會顯示為例外狀況類型,如下列範例所示。

    Exception object: 39594518
    Exception type: System.OutOfMemoryException
    Message: <none>
    InnerException: <none>
    StackTrace (generated):
    
  2. 如果輸出未指定例外狀況,則必須判斷記憶體不足例外狀況是來自哪個執行緒。 在偵錯工具中輸入下列命令,顯示所有執行緒與其呼叫堆疊:

    ~*kb

    堆疊中包含例外狀況呼叫的執行緒會以 RaiseTheException 引數表示。 這是 Managed 例外狀況物件。

    28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0 
    
  3. 您可以使用下列命令傾印巢狀例外狀況。

    !pe -nested

    如果您未找到任何例外狀況,表示記憶體不足例外狀況是來自 Unmanaged 程式碼。

若要判斷可以保留多少虛擬記憶體

  • 在載入 SOS 偵錯工具擴充功能的 WinDbg 中,輸入下列命令取得最大可用區域:

    !address -summary

    最大可用區域便會顯示,如下列輸出所示。

    Largest free region: Base 54000000 - Size 0003A980
    

    在這個範例中,最大可用區域的大小約為 24000 KB (十六進位值為 3A980)。 這個區域比記憶體回收行程針對區段所需的區域還小許多。

    -或-

  • 使用 vmstat 命令:

    !vmstat

    最大可用區域是 MAXIMUM 資料行中的最大值,如下列輸出所示。

    TYPE        MINIMUM   MAXIMUM     AVERAGE   BLK COUNT   TOTAL
    ~~~~        ~~~~~~~   ~~~~~~~     ~~~~~~~   ~~~~~~~~~~  ~~~~
    Free:
    Small       8K        64K         46K       36          1,671K
    Medium      80K       864K        349K      3           1,047K
    Large       1,384K    1,278,848K  151,834K  12          1,822,015K
    Summary     8K        1,278,848K  35,779K   51          1,824,735K
    

若要判斷實體記憶體是否足夠

  1. 啟動 [Windows 工作管理員]。

  2. 查看 [效能] 索引標籤上認可的值 (在 Windows 7 中,查看 [系統群組] 中的 [認可 (KB)])。

    如果 [總共] 接近 [限制],表示您的實體記憶體正逐漸減少。

若要判斷 Managed 堆積認可的記憶體數目

  • 使用 # Total committed bytes 記憶體效能計數器取得 Managed 堆積認可的位元組數。 記憶體回收行程會視需要認可區段上的區塊,而不會全部同時認可。

    注意事項注意事項

    請不要使用 # Bytes in all Heaps 效能計數器,因為它不代表 Managed 堆積的實際記憶體使用量。層代的大小包括在這個值中,實際上就是其臨界值的大小,也就是層代中填入物件時誘發記憶體回收的大小。因此,這個值通常為零。

若要判斷 Managed 堆積保留的記憶體數目

  • 使用 # Total reserved bytes 記憶體效能計數器。

    記憶體回收行程會將記憶體保留在區段中,您可以使用 eeheap 命令判斷區段開始的位置。

  • 在載入 SOS 偵錯工具擴充功能的 WinDbg 或 Visual Studio 偵錯工具中,輸入下列命令:

    !eeheap -gc

    結果如下所示。

    Number of GC Heaps: 2
    ------------------------------
    Heap 0 (002db550)
    generation 0 starts at 0x02abe29c
    generation 1 starts at 0x02abdd08
    generation 2 starts at 0x02ab0038
    ephemeral segment allocation context: none
     segment    begin allocated     size
    02ab0000 02ab0038  02aceff4 0x0001efbc(126908)
    Large object heap starts at 0x0aab0038
     segment    begin allocated     size
    0aab0000 0aab0038  0aab2278 0x00002240(8768)
    Heap Size   0x211fc(135676)
    ------------------------------
    Heap 1 (002dc958)
    generation 0 starts at 0x06ab1bd8
    generation 1 starts at 0x06ab1bcc
    generation 2 starts at 0x06ab0038
    ephemeral segment allocation context: none
     segment    begin allocated     size
    06ab0000 06ab0038  06ab3be4 0x00003bac(15276)
    Large object heap starts at 0x0cab0038
     segment    begin allocated     size
    0cab0000 0cab0038  0cab0048 0x00000010(16)
    Heap Size    0x3bbc(15292)
    ------------------------------
    GC Heap Size   0x24db8(150968)
    

    "segment" 指出的位址是區段的開始位址。

若要判斷層代 2 中的大型物件

  • 在載入 SOS 偵錯工具擴充功能的 WinDbg 或 Visual Studio 偵錯工具中,輸入下列命令:

    !dumpheap –stat

    如果 Managed 堆積很大,dumpheap 可能需要一段時間才能完成。

    您可以從輸出的最後幾行開始分析,因為這幾行會列出使用最多空間的物件。 例如:

    2c6108d4   173712     14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo
    00155f80      533     15216804      Free
    7a747c78   791070     15821400 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700930     19626040 System.Collections.Specialized.ListDictionary
    2c64e36c    78644     20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo
    79124228   121143     29064120 System.Object[]
    035f0ee4    81626     35588936 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    791242ec    40182     90664128 System.Collections.Hashtable+bucket[]
    790fa3e0  3154024    137881448 System.String
    Total 8454945 objects
    

    列出的最後一個物件是字串,而且佔據最多空間。 您可以檢查應用程式,了解能夠如何最佳化您的字串物件。 若要查看介於 150 和 200 個位元組之間的字串,請輸入下列命令:

    !dumpheap -type System.String -min 150 -max 200

    結果的範例如下所示。

    Address  MT           Size  Gen
    1875d2c0 790fa3e0      152    2 System.String HighlightNullStyle_Blotter_PendingOrder-11_Blotter_PendingOrder-11
    …
    

    使用整數,而不要使用字串做為 ID,這樣會更有效率。 如果相同字串要重複數千次,請考慮字串暫留 (Interning)。 如需字串暫留的詳細資訊,請參閱 String.Intern 方法的參考主題。

若要判斷物件的參考

  • 在載入 SOS 偵錯工具擴充功能的 WinDbg 中,輸入下列命令列出物件的參考:

    !gcroot

    -or-

  • 若要判斷特定物件的參考,請包括下列位址:

    !gcroot 1c37b2ac

    堆疊上找到的根目錄可能是誤報。 如需詳細資訊,請使用 !help gcroot 命令。

    ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)->
    19010b78(DemoApp.FormDemoApp)->
    19011158(System.Windows.Forms.PropertyStore)->
    … [omitted]
    1c3745ec(System.Data.DataTable)->
    1c3747a8(System.Data.DataColumnCollection)->
    1c3747f8(System.Collections.Hashtable)->
    1c376590(System.Collections.Hashtable+bucket[])->
    1c376c98(System.Data.DataColumn)->
    1c37b270(System.Data.Common.DoubleStorage)->
    1c37b2ac(System.Double[])
    Scan Thread 0 OSTHread 99c
    Scan Thread 6 OSTHread 484
    

    gcroot 命令可能需要一段時間才能完成。 記憶體回收未收回的任何物件都是實際物件。 這表示,某個根目錄直接或間接保留在物件上,因此 gcroot 應將路徑資訊傳回至物件。 您應檢查傳回的圖形,並且了解為什麼仍然參考這些物件。

若要判斷完成項是否已經執行

  • 執行包含下列程式碼的測試程式:

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    

    如果測試解決了這個問題,表示記憶體回收行程未回收物件,因為這些物件的完成項已暫止。 GC.WaitForPendingFinalizers 方法會讓完成項完成其工作,並且修正問題。

若要判斷是否有等待完成的物件

  1. 在載入 SOS 偵錯工具擴充功能的 WinDbg 或 Visual Studio 偵錯工具中,輸入下列命令:

    !finalizequeue

    查看準備要進行最終處理的物件數目。 如果數目很大,則必須檢查這些完成項完全無法進行處理或處理速度不夠快的原因。

  2. 若要取得執行緒的輸出,請輸入下列命令:

    threads -special

    這個命令會提供如下所述的輸出。

           OSID     Special thread type
        2    cd0    DbgHelper 
        3    c18    Finalizer 
        4    df0    GC SuspendEE 
    

    完成項執行緒會指出目前正在執行哪個完成項 (如果有的話)。 完成項執行緒未執行任何完成項時,它會等候事件告知要執行的工作。 大部分時候您都將看到完成項執行緒處於這個狀態,因為它是以 THREAD_HIGHEST_PRIORITY 執行,而且應該很快地完成執行完成項 (如果有的話)。

若要判斷 Managed 堆積中的可用空間量

  • 在載入 SOS 偵錯工具擴充功能的 WinDbg 或 Visual Studio 偵錯工具中,輸入下列命令:

    !dumpheap -type Free -stat

    這個命令會顯示 Managed 堆積上所有可用物件的總大小,如下列範例所示。

    total 230 objects
    Statistics:
          MT    Count    TotalSize Class Name
    00152b18      230     40958584      Free
    Total 230 objects
    
  • 若要判斷層代 0 中的可用空間,請輸入下列命令取得層代的記憶體消耗量資訊:

    !eeheap -gc

    這個命令會顯示類似下列的輸出。 最後一行顯示暫時區段。

    Heap 0 (0015ad08)
    generation 0 starts at 0x49521f8c
    generation 1 starts at 0x494d7f64
    generation 2 starts at 0x007f0038
    ephemeral segment allocation context: none
    segment  begin     allocated  size
    00178250 7a80d84c  7a82f1cc   0x00021980(137600)
    00161918 78c50e40  78c7056c   0x0001f72c(128812)
    007f0000 007f0038  047eed28   0x03ffecf0(67103984)
    3a120000 3a120038  3a3e84f8   0x002c84c0(2917568)
    46120000 46120038  49e05d04   0x03ce5ccc(63855820)
    
  • 計算層代 0 所使用的空間:

    ? 49e05d04-0x49521f8c

    結果如下所示。 層代 0 約為 9 MB。

    Evaluate expression: 9321848 = 008e3d78
    
  • 下列命令會傾印層代 0 範圍內可用空間:

    !dumpheap -type Free -stat 0x49521f8c 49e05d04

    結果如下所示。

    ------------------------------
    Heap 0
    total 409 objects
    ------------------------------
    Heap 1
    total 0 objects
    ------------------------------
    Heap 2
    total 0 objects
    ------------------------------
    Heap 3
    total 0 objects
    ------------------------------
    total 409 objects
    Statistics:
          MT    Count TotalSize Class Name
    0015a498      409   7296540      Free
    Total 409 objects
    

    這個輸出顯示,堆積的層代 0 部分有 9 MB 空間用於物件,且擁有 7 MB 可用空間。 這項分析顯示層代 0 影響分散的程度。 這個堆積使用數目應做為長期存在物件的分散原因從總數中減去。

若要判斷已固定的物件數目

  • 在載入 SOS 偵錯工具擴充功能的 WinDbg 或 Visual Studio 偵錯工具中,輸入下列命令:

    !gchandles

    顯示的統計資料包括固定的處理代碼數目,如下列範例所示。

    GC Handle Statistics:
    Strong Handles:      29
    Pinned Handles:      10
    

若要判斷記憶體回收時間的長度

  • 檢查 % Time in GC 記憶體效能計數器。

    這個值是使用取樣間隔時間計算。 由於計數器會在每次記憶體回收結束時更新,因此,如果間隔期間未發生任何回收,則目前取樣的值將與之前取樣的值相同。

    回收時間是透過取樣間隔時間乘以百分比值取得。

    下列資料針對一個 8 秒的研究顯示四個兩秒的取樣間隔。 Gen0、Gen1 和 Gen2 資料行顯示該層代間隔期間發生的記憶體回收次數。

    Interval    Gen0    Gen1    Gen2    % Time in GC
           1       9       3       1              10
           2      10       3       1               1
           3      11       3       1               3
           4      11       3       1               3
    

    這項資訊不會顯示記憶體回收發生的時間,但是可以判斷時間間隔內發生的記憶體回收次數。 假設最糟情況是,第十次層代 0 記憶體回收在第二個間隔開始時完成,第十一次層代 0 記憶體回收在第五個間隔結束時完成。 第十次記憶體回收結束和第十一次記憶體回收結束之間的時間約為 2 秒,而且效能計數器顯示 3%,因此第十一次層代 0 記憶體回收的持續時間為 (2 秒 * 3% = 60 毫秒)。

    這個範例中有 5 個週期。

    Interval    Gen0    Gen1    Gen2     % Time in GC
           1       9       3       1                3
           2      10       3       1                1
           3      11       4       2                1
           4      11       4       2                1
           5      11       4       2               20
    

    第二次層代 2 記憶體回收在第三個間隔期間開始,而且在第五個間隔時完成。 假設最糟情況是,最後一次記憶體回收是針對層代 0 回收,該回收是在第二個間隔開始時完成,以及在第五個間隔結束時完成的層代 2 記憶體回收。 因此,從層代 0 記憶體回收結束到層代 2 記憶體回收結束之間的時間為 4 秒。 由於 % Time in GC 計數器為 20%,因此層代 2 記憶體回收可能耗用的最長時間為 (4 秒 * 20% = 800 毫秒)。

  • 或者,您可以使用記憶體回收 ETW 事件判斷記憶體回收的時間長度,並且分析資訊以判斷記憶體回收的持續期間。

    例如,下列資料顯示非並行記憶體回收期間發生的事件序列。

    Timestamp    Event name
    513052        GCSuspendEEBegin_V1
    513078        GCSuspendEEEnd
    513090        GCStart_V1
    517890        GCEnd_V1
    517894        GCHeapStats
    517897        GCRestartEEBegin
    517918        GCRestartEEEnd
    

    暫止 Managed 執行緒耗費 26 毫秒 (GCSuspendEEEnd – GCSuspendEEBegin_V1)。

    實際記憶體回收耗費 4.8 毫秒 (GCEnd_V1 – GCStart_V1)。

    繼續執行 Managed 執行緒耗費 21 毫秒 (GCRestartEEEnd – GCRestartEEBegin)。

    下列輸出提供背景記憶體回收的範例,並且包括處理序、執行緒和事件欄位 (並非所有資料都顯示)。

    timestamp(us)    event name            process    thread    event field
    42504385        GCSuspendEEBegin_V1    Test.exe    4372             1
    42504648        GCSuspendEEEnd         Test.exe    4372        
    42504816        GCStart_V1             Test.exe    4372        102019
    42504907        GCStart_V1             Test.exe    4372        102020
    42514170        GCEnd_V1               Test.exe    4372        
    42514204        GCHeapStats            Test.exe    4372        102020
    42832052        GCRestartEEBegin       Test.exe    4372        
    42832136        GCRestartEEEnd         Test.exe    4372        
    63685394        GCSuspendEEBegin_V1    Test.exe    4744             6
    63686347        GCSuspendEEEnd         Test.exe    4744        
    63784294        GCRestartEEBegin       Test.exe    4744        
    63784407        GCRestartEEEnd         Test.exe    4744        
    89931423        GCEnd_V1               Test.exe    4372        102019
    89931464        GCHeapStats            Test.exe    4372        
    

    於 42504816 的 GCStart_V1 事件指出,這是背景記憶體回收,因為最後一個欄位是 1。 這會變成記憶體回收編號 102019.

    GCStart 事件會發生,因為在開始背景記憶體回收之前,需要暫時的記憶體回收。 這會變成記憶體回收編號 102020.

    在 42514170,記憶體回收編號 102020 完成。 Managed 執行緒會在此重新啟動。 這會在執行緒 4372 上完成,該執行緒觸發了這個背景記憶體回收。

    執行緒 4744 上會發生暫止。 這是背景記憶體回收必須暫止 Managed 執行緒的唯一時刻。 這個持續期間約為 99 毫秒((63784407-63685394)/1000)。

    背景記憶體回收的 GCEnd 事件是在 89931423。 這表示,背景記憶體回收持續約 47 秒 ((89931423-42504816)/1000)。

    當 Managed 執行緒執行時,您可能會看見任意數目的暫時記憶體回收發生。

若要判斷觸發記憶體回收的原因

  • 在載入 SOS 偵錯工具擴充功能的 WinDbg 或 Visual Studio 偵錯工具中,輸入下列命令,以顯示所有執行緒與其呼叫堆疊:

    ~*kb

    這個命令會顯示類似下列的輸出。

    0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect 
    0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4
    0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48
    

    如果記憶體回收是因為作業系統發出的記憶體不足通知所造成,則雖然呼叫堆疊類似,但執行緒會是完成項執行緒。 完成項執行緒會收到非同步記憶體不足通知,並且誘發記憶體回收。

    如果記憶體回收是因為記憶體配置所造成,則堆疊如下所示:

    0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration
    0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1
    0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18
    0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b
    0012f310 7a02ae4c mscorwks!Alloc+0x60
    0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd
    0012f424 300027f4 mscorwks!JIT_NewArr1+0x148
    000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c
    0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153
    

    Just-In-Time Helper (JIT_New*) 最終會呼叫 GCHeap::GarbageCollectGeneration。 如果您判斷層代 2 記憶體回收是因為配置所造成,則必須判斷出層代 2 記憶體回收所回收的物件,以及如何避免這些物件。 也就是說,您想要判斷出層代 2 記憶體回收開始和結束之間的差異,以及造成層代 2 回收的物件。

    例如,在偵錯工具中輸入下列命令,顯示層代 2 回收的開始:

    !dumpheap –stat

    範例輸出 (簡略顯示使用最多空間的物件):

    79124228    31857      9862328 System.Object[]
    035f0384    25668     11601936 Toolkit.TlkPosition
    00155f80    21248     12256296      Free
    79103b6c   297003     13068132 System.Threading.ReaderWriterLock
    7a747ad4   708732     14174640 System.Collections.Specialized.HybridDictionary
    7a747c78   786498     15729960 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700298     19608344 System.Collections.Specialized.ListDictionary
    035f0ee4    89192     38887712 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    7912c444    91616     71887080 System.Double[]
    791242ec    32451     82462728 System.Collections.Hashtable+bucket[]
    790fa3e0  2459154    112128436 System.String
    Total 6471774 objects
    

    在層代 2 結束時重複命令:

    !dumpheap –stat

    範例輸出 (簡略顯示使用最多空間的物件):

    79124228    26648      9314256 System.Object[]
    035f0384    25668     11601936 Toolkit.TlkPosition
    79103b6c   296770     13057880 System.Threading.ReaderWriterLock
    7a747ad4   708730     14174600 System.Collections.Specialized.HybridDictionary
    7a747c78   786497     15729940 System.Collections.Specialized.ListDictionary+DictionaryNode
    7a747bac   700298     19608344 System.Collections.Specialized.ListDictionary
    00155f80    13806     34007212      Free
    035f0ee4    89187     38885532 Toolkit.TlkOrder
    00fcae40     6193     44911636 WaveBasedStrategy.Tick_Snap[]
    791242ec    32370     82359768 System.Collections.Hashtable+bucket[]
    790fa3e0  2440020    111341808 System.String
    Total 6417525 objects
    

    double[] 物件會從輸出結尾消失,表示這些物件已回收。 這些物件約佔 70 MB。 其餘物件則不會大幅改變。 因此,這些 double[] 物件就是這個層代 2 記憶體回收發生的原因。 您的下一步是判斷這些 double[] 物件出現以及變成無作用的原因。 您可以詢問程式碼開發人員這些物件的來源,或是使用 gcroot 命令。

若要判斷記憶體回收是否造成高 CPU 使用量

  • 將 % Time in GC 記憶體效能計數器值與處理時間相互關聯。

    如果 % Time in GC 值與處理時間同時突然增加,表示記憶體回收正造成高 CPU 使用量。 否則,請剖析應用程式,找出高使用量發生的位置。

請參閱

概念

記憶體回收