針對 .NET 中的記憶體流失進行偵錯
本文適用於: ✔️ .NET Core 3.1 SDK 與更新版本
應用程式參考不再需要執行所需工作的物件時,記憶體可能會流失。 參考這些物件可防止記憶體回收行程回收所使用的記憶體。 這可能會導致效能降低和擲回 OutOfMemoryException 例外狀況。
本教學課程示範如何使用 .NET 診斷 CLI 工具來分析 .NET 應用程式中記憶體流失的工具。 如果您位於 Windows,則可以使用 Visual Studio 的記憶體診斷工具來針對記憶體流失進行偵錯。
本教學課程使用刻意讓記憶體流失的範例應用程式,以做為練習。 您也可以分析意外讓記憶體流失的應用程式。
在此教學課程中,您需要:
- 使用 dotnet-counters 來檢查受控記憶體使用量。
- 產生傾印檔案。
- 使用傾印檔案來分析記憶體使用量。
必要條件
教學課程使用:
- .NET Core 3.1 SDK 或更新版本。
- dotnet-counters,以檢查受控記憶體使用量。
- dotnet-dump,以收集和分析傾印檔案 (包括 SOS 偵錯延伸模組)。
- 要診斷的範例偵錯目標應用程式。
本教學課程假設已安裝範例應用程式和工具,並可供使用。
檢查受控記憶體使用量
開始收集診斷資料以協助找出此情節的根本原因之前,請確定您實際看到記憶體流失 (記憶體使用量成長)。 您可以使用 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
您可以在這裡看到大部分的物件都是 String
或 Customer
物件。
您可以再次搭配使用 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 伺服器。 此伺服器應該已予以關閉,如重新啟動失敗的處理序一節中所述。
您也可以刪除已建立的傾印檔案。
另請參閱
- dotnet-trace,以列出處理序
- dotnet-counters 以檢查受控記憶體使用量
- dotnet-dump 以收集和分析傾印檔案
- dotnet/diagnostics
- 使用 Visual Studio 以針對記憶體流失進行偵錯