本文適用於: ✔️ .NET Core 3.1 SDK 與更新版本
當您的應用程式參考到已不再需要的物件來執行所需任務時,記憶體可能會洩漏。 參照這些物件可防止記憶體回收器回收已使用的記憶體。 這可能會導致效能降低並拋出OutOfMemoryException例外狀況。
本教學課程示範使用 .NET 診斷 CLI 工具分析 .NET 應用程式中記憶體流失的工具。 如果您使用的是 Windows,您可以使用 Visual Studio 的記憶體診斷工具 來偵錯記憶體流失。
本教學使用故意製造記憶體洩漏的範例應用用作練習。 您也可以分析無意中洩漏記憶體的應用程式。
在本教學課程中,您將:
- 使用 dotnet-counters 檢查受控記憶體使用量。
- 轉儲檔案。
- 使用匯出文件分析記憶體狀況。
先決條件
本教學課程使用:
- .NET Core 3.1 SDK 或更新版本。
- dotnet-counters 來檢查受控記憶體使用量。
- dotnet-dump 來收集和分析傾印檔案 (包括 SOS 偵錯延伸模組)。
- 要診斷的 偵錯目標應用程式範例 。
本教學課程假設範例應用程式和工具已安裝並可供使用。
如果您的應用程式執行的 .NET 版本早於 .NET 9,則 dotnet-counters 的輸出 UI 看起來會稍有不同;如需詳細資訊,請參閱 dotnet-counters 。
檢查受控記憶體使用量
開始收集診斷資料以協助造成此案例的根本原因之前,請確定您確實看到記憶體流失 (記憶體使用量的成長) 。 您可以使用 dotnet-counters 工具來確認。
開啟主控台視窗,然後導覽至您下載並解壓縮 範例偵錯目標的目錄。 執行目標程式:
dotnet run
從個別主控台中,尋找處理程序 ID:
dotnet-counters ps
輸出結果應該類似:
4807 DiagnosticScena /home/user/git/samples/core/diagnostics/DiagnosticScenarios/bin/Debug/netcoreapp3.0/DiagnosticScenarios
備註
如果上一個命令不起作用或找不到,您可能需要先安裝該 dotnet-counters 工具。 使用下列命令:
dotnet tool install --global dotnet-counters
現在,使用 dotnet-counters 工具檢查受控記憶體使用量。 指定 --refresh-interval 重新整理之間的秒數:
dotnet-counters monitor --refresh-interval 1 -p 4807
即時輸出應類似於:
Press p to pause, r to resume, q to quit.
Status: Running
Name Current Value
[System.Runtime]
dotnet.assembly.count ({assembly}) 111
dotnet.gc.collections ({collection})
gc.heap.generation
------------------
gen0 1
gen1 0
gen2 0
dotnet.gc.heap.total_allocated (By) 4,431,712
dotnet.gc.last_collection.heap.fragmentation.size (By)
gc.heap.generation
------------------
gen0 803,576
gen1 15,456
gen2 0
loh 0
poh 0
dotnet.gc.last_collection.heap.size (By)
gc.heap.generation
------------------
gen0 811,960
gen1 1,214,720
gen2 0
loh 0
poh 24,528
dotnet.gc.last_collection.memory.committed_size (By) 4,296,704
dotnet.gc.pause.time (s) 0.003
dotnet.jit.compilation.time (s) 0.329
dotnet.jit.compiled_il.size (By) 120,212
dotnet.jit.compiled_methods ({method}) 1,202
dotnet.monitor.lock_contentions ({contention}) 2
dotnet.process.cpu.count ({cpu}) 22
dotnet.process.cpu.time (s)
cpu.mode
--------
system 0.344
user 0.344
dotnet.process.memory.working_set (By) 64,331,776
dotnet.thread_pool.queue.length ({work_item}) 0
dotnet.thread_pool.thread.count ({thread}) 0
dotnet.thread_pool.work_item.count ({work_item}) 7
dotnet.timer.count ({timer}) 0
重點關注這條線:
dotnet.gc.last_collection.memory.committed_size (By) 4,296,704
您可以看到受管理的堆儲記憶體在啟動後為 4 MB。
現在,轉到 URL https://localhost:5001/api/diagscenario/memleak/20000。
觀察記憶體使用量已成長至 20 MB 以上。
dotnet.gc.last_collection.memory.committed_size (By) 21,020,672
通過觀察內存使用情況,您可以放心地說內存正在增長或洩漏。 下一步是收集正確的數據進行記憶體分析。
產生記憶體轉儲
分析可能的記憶體洩漏時,您需要存取應用程式的記憶體堆積來分析記憶體內容。 觀察物體之間的關係,你可以創造出關於為什麼記憶沒有被釋放的理論。 常見的診斷資料來源是 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
您現在可以在gcroot的實例上使用System.String命令來查看該物件是如何以及為什麼被設置為根:
> 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 偵錯記憶體流失