針對 .NET 中的記憶體流失進行偵錯

本文適用於: ✔️ .NET Core 3.1 SDK 與更新版本

應用程式參考不再需要執行所需工作的物件時,記憶體可能會流失。 參考這些物件可防止記憶體回收行程回收所使用的記憶體。 這可能會導致效能降低和擲回 OutOfMemoryException 例外狀況。

本教學課程示範如何使用 .NET 診斷 CLI 工具來分析 .NET 應用程式中記憶體流失的工具。 如果您位於 Windows,則可以使用 Visual Studio 的記憶體診斷工具來針對記憶體流失進行偵錯。

本教學課程使用刻意讓記憶體流失的範例應用程式,以做為練習。 您也可以分析意外讓記憶體流失的應用程式。

在此教學課程中,您需要:

  • 使用 dotnet-counters 來檢查受控記憶體使用量。
  • 產生傾印檔案。
  • 使用傾印檔案來分析記憶體使用量。

必要條件

教學課程使用:

本教學課程假設已安裝範例應用程式和工具,並可供使用。

檢查受控記憶體使用量

開始收集診斷資料以協助找出此情節的根本原因之前,請確定您實際看到記憶體流失 (記憶體使用量成長)。 您可以使用 dotnet-counters 工具來進行確認。

開啟主控台視窗,然後導覽至您已下載並解壓縮範例偵錯目標的目錄。 執行目標:

dotnet run

從個別的主控台中,尋找處理序識別碼:

dotnet-counters ps

輸出應該類似:

4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios

現在,使用 dotnet-counters 工具來檢查受控記憶體使用量。 --refresh-interval 指定重新整理之間的秒數:

dotnet-counters monitor --refresh-interval 1 -p 4807

即時輸出應該類似:

Press p to pause, r to resume, q to quit.
    Status: Running

[System.Runtime]
    # of Assemblies Loaded                           118
    % Time in GC (since last GC)                       0
    Allocation Rate (Bytes / sec)                 37,896
    CPU Usage (%)                                      0
    Exceptions / sec                                   0
    GC Heap Size (MB)                                  4
    Gen 0 GC / sec                                     0
    Gen 0 Size (B)                                     0
    Gen 1 GC / sec                                     0
    Gen 1 Size (B)                                     0
    Gen 2 GC / sec                                     0
    Gen 2 Size (B)                                     0
    LOH Size (B)                                       0
    Monitor Lock Contention Count / sec                0
    Number of Active Timers                            1
    ThreadPool Completed Work Items / sec             10
    ThreadPool Queue Length                            0
    ThreadPool Threads Count                           1
    Working Set (MB)                                  83

專注於這一行:

    GC Heap Size (MB)                                  4

您可以在啟動之後立即看到受控堆積記憶體為 4 MB。

現在,移至 URL https://localhost:5001/api/diagscenario/memleak/20000

觀察記憶體使用量已成長至 30 MB。

    GC Heap Size (MB)                                 30

監看記憶體使用量,即可放心地說記憶體正在成長或流失。 下一個步驟是收集正確的資料來進行記憶體分析。

產生記憶體傾印

分析可能的記憶體流失時,您需要存取應用程式的記憶體堆積來分析記憶體內容。 您可以查看物件之間的關聯性,來建立為何未釋出記憶體的理論。 常見的診斷資料來源是 Windows 上的記憶體傾印,或 Linux 上的對等核心傾印。 若要產生 .NET 應用程式的傾印,您可以使用 dotnet-dump 工具。

使用先前啟動的範例偵錯目標,執行下列命令以產生 Linux 核心傾印:

dotnet-dump collect -p 4807

結果是位於相同資料夾中的核心傾印。

Writing minidump with heap to ./core_20190430_185145
Complete

注意

為了進行一段時間的比較,讓原始處理序在收集第一個傾印之後繼續執行,並以相同的方式收集第二個傾印。 然後,您會在一段時間內有兩個傾印,而您可以對其進行比較以查看記憶體使用量成長的位置。

重新啟動失敗的處理序

收集傾印之後,您應該就有足夠的資訊來診斷失敗的處理序。 如果失敗的處理序正在生產伺服器上執行,則現在重新啟動此處理序是短期補救的理想時間。

在本教學課程中,您現在已完成範例偵錯目標,並且可以將其關閉。 導覽至已啟動伺服器的終端機,然後按 Ctrl+C

分析核心傾印

既然您已產生核心傾印,則請使用 dotnet-dump 工具來分析傾印:

dotnet-dump analyze core_20190430_185145

其中 core_20190430_185145 是您想要分析的核心傾印名稱。

注意

如果您看到抱怨找不到 libdl.so 的錯誤,則可能必須安裝 libc6-dev 套件。 如需詳細資訊,請參閱 Linux 上 .NET 的必要條件

您將會看到可輸入 SOS 命令的提示。 通常,您想要查看的第一件事是受控堆積的整體狀態:

> dumpheap -stat

Statistics:
              MT    Count    TotalSize Class Name
...
00007f6c1eeefba8      576        59904 System.Reflection.RuntimeMethodInfo
00007f6c1dc021c8     1749        95696 System.SByte[]
00000000008c9db0     3847       116080      Free
00007f6c1e784a18      175       128640 System.Char[]
00007f6c1dbf5510      217       133504 System.Object[]
00007f6c1dc014c0      467       416464 System.Byte[]
00007f6c21625038        6      4063376 testwebapi.Controllers.Customer[]
00007f6c20a67498   200000      4800000 testwebapi.Controllers.Customer
00007f6c1dc00f90   206770     19494060 System.String
Total 428516 objects

您可以在這裡看到大部分的物件都是 StringCustomer 物件。

您可以再次搭配使用 dumpheap 命令與方法資料表 (MT),來取得所有 String 執行個體的清單:

> dumpheap -mt 00007f6c1dc00f90

         Address               MT     Size
...
00007f6ad09421f8 00007faddaa50f90       94
...
00007f6ad0965b20 00007f6c1dc00f90       80
00007f6ad0965c10 00007f6c1dc00f90       80
00007f6ad0965d00 00007f6c1dc00f90       80
00007f6ad0965df0 00007f6c1dc00f90       80
00007f6ad0965ee0 00007f6c1dc00f90       80

Statistics:
              MT    Count    TotalSize Class Name
00007f6c1dc00f90   206770     19494060 System.String
Total 206770 objects

您現在可以在 System.String 執行個體上使用 gcroot 命令,來查看物件根目錄的方式和原因:

> gcroot 00007f6ad09421f8

Thread 3f68:
    00007F6795BB58A0 00007F6C1D7D0745 System.Diagnostics.Tracing.CounterGroup.PollForValues() [/_/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/CounterGroup.cs @ 260]
        rbx:  (interior)
            ->  00007F6BDFFFF038 System.Object[]
            ->  00007F69D0033570 testwebapi.Controllers.Processor
            ->  00007F69D0033588 testwebapi.Controllers.CustomerCache
            ->  00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
            ->  00007F6C000148A0 testwebapi.Controllers.Customer[]
            ->  00007F6AD0942258 testwebapi.Controllers.Customer
            ->  00007F6AD09421F8 System.String

HandleTable:
    00007F6C98BB15F8 (pinned handle)
    -> 00007F6BDFFFF038 System.Object[]
    -> 00007F69D0033570 testwebapi.Controllers.Processor
    -> 00007F69D0033588 testwebapi.Controllers.CustomerCache
    -> 00007F69D00335A0 System.Collections.Generic.List`1[[testwebapi.Controllers.Customer, DiagnosticScenarios]]
    -> 00007F6C000148A0 testwebapi.Controllers.Customer[]
    -> 00007F6AD0942258 testwebapi.Controllers.Customer
    -> 00007F6AD09421F8 System.String

Found 2 roots.

您可以看到 String 直接由 Customer 物件所持有,並間接由 CustomerCache 物件所持有。

您可以繼續傾印物件,以查看大部分 String 物件都遵循類似的模式。 此時,調查已提供足夠的資訊來識別程式碼中的根本原因。

此一般處理序可讓您識別主要記憶體流失的來源。

清除資源

在本教學課程中,您已啟動範例 Web 伺服器。 此伺服器應該已予以關閉,如重新啟動失敗的處理序一節中所述。

您也可以刪除已建立的傾印檔案。

另請參閱

下一步