記錄一次記憶體洩漏排查過程

傅小灰發表於2023-11-06

某天收到運維線上警報,伺服器記憶體告警,需要處理一下。此時透過瀏覽器開啟頁面,系統可以正常訪問,但是有明顯示卡頓。為了不影響客戶使用,先重啟了服務釋放了記憶體。由於該專案平時訪問量並不大,因此隨著程式執行記憶體佔用率的增長比較緩慢,直到第三天才發現從原本的10%跳到了45%。初步懷疑有記憶體洩漏問題需要進行線上排查。

除錯記憶體洩漏教程 - .NET | Microsoft Learn

伺服器環境

  • Linux:CentOS Linux release 7.6.1810 (Core)
  • .NET SDK:.NET SDK (5.0.408)

安裝診斷工具

在伺服器上安裝dotnet tool下的診斷工具

  • dotnet-counters 是一個效能監視工具,用於臨時執行狀況監視和初級效能調查。
  • dotnet-dump 是在未涉及任何本機偵錯程式的情況下收集和分析 Windows、Linux 和 macOS 轉儲的方法。 可以執行 SOS 命令來分析崩潰和垃圾回收器 (GC)
dotnet tool install -g dotnet-counters
dotnet tool install -g dotnet-dump

安裝過程中可能遇到的問題:

1. Https 訪問證書問題

NuGet.Protocol.Core.Types.FatalProtocolException: 無法載入源 https://api.nuget.org/v3/index.json 的服務索引。
---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot

執行openssl version -a查詢安裝目錄,然後執行cp /etc/pki/tls/cert.pem /usr/local/openssl-1.1.1o/ssl/命令將預設證書放到SSL目錄

2. dotnet tool 版本問題

Unable to find package dotnet-counters. No packages exist with this id in source(s): nuget.org

error NU1202: Package dotnet-counters 8.0.452401 is not compatible with net5.0 (.NETCoreApp,Version=v5.0) / any. Package dotnet-counters 8.0.452401 supports: net6.0 (.NETCoreApp,Version=v6.0)

根據伺服器上的.NET SDK版本,指定版本號完成的安裝。版本號:https://www.nuget.org/packages/dotnet-counters

分析過程

透過Top命令檢視程式資源佔用情況,輸入M按記憶體使用百分比排序,並獲取到程式PID。

執行dotnet-counters monitor -p 6309,檢視程式執行資訊,發現Gen 2和LOH佔用大量記憶體。

關於GC相關內容可以看官方文件ASP.NET Core 中的記憶體管理和模式

(上面兩張截圖是事後擷取的,作演示用)

執行dotnet-dump collect -p 6309,在當前目錄生成程式記憶體dump檔案

執行dotnet-dump analyze core_20231026_162034分析dump檔案,進入互動式shell,透過SOS命令分析檔案內容。相關SOS命令可以檢視官方文件dotnet-dump 診斷工具

執行dumpheap -stat -min 85000,檢視大於85000 B的物件,可以看到有78個大System.Byte[]型別的物件。

執行dumpheap -mt -min 85000檢視具體的大物件列表。GC 會為大型物件(大於 85,000 位元組)建立特殊記憶體區域,稱為大型物件堆 (LOH)。

執行dumpobj 00007fdfbd54ead0,檢視物件內容。

比對下各個物件內容,大部分為亂碼,少數顯示ExifJFIF等字元。聯想到最近專案更新中包含了一部分圖片處理相關功能,因此初步懷疑是ImageResize的程式碼有問題,導致了記憶體洩漏。

執行gcroot 00007f8c51457968,檢視物件引用情況,沒有任何物件引用。

重寫修改了程式碼後上線,隨著相關介面被呼叫,程式的記憶體佔用依舊會上漲。透過dotnet-counters檢視發現Gen2和LOH佔用大量記憶體沒有被回收。

查閱官方文件的過程中發現,臨時大物件會導致第 2 代 GC,但是不理解為什麼這些記憶體沒有被回收。

解決方案

2023-10-27 更新:原本專案中使用System.Drawing對圖片進行壓縮處理,綜合考慮後選擇使用SixLabors.ImageSharp重寫這部分程式碼。目前更新線上上觀察一段時間。

相關文章