同事報案,某測試站台不定期會發生 OutOfMemeoryException 記憶體不足錯誤,接獲通報立刻趕往事故現場,問題網站已吃掉 1.8GB 記憶體,差不多是 32 位元模式可用記憶體的上限。廢話不多說,開啟 32 位元工具管理員(C:\Windows\SysWOW64\TaskMgr.exe 參考) 擷取 Memory Dump 檔。

從工具箱搬出 CPU/Memory 茶包分析的小型戰術核武 - DebugDiag 2 Tools,之前處理的都是 CPU 滿載案例,記憶體用盡分析倒是頭一遭。選用 DotNetMemoryAnalysis - Managed Memory Analysis。(另一個選項 MemoryAnalysis – Memory analysis including LeakTrack and heap reporting 主要用於連取多個 Dump 檔抓漏,而本案例重點在找出記憶體被誰用掉)

1.8GB 檔案不小,看了一陣子進度條,取得 DebugDiag Analysis 分析報告:

身為記憶體問題生手,只能約略看出大概,問題有三:(第四點測試台開 debug 天經地義,可忽略)

  1. Dump 檔載入了 1023 個動態組件
    分析報表貼心附上 Debugger Laddy, Tess 的文章,指出有可能與 XML 元件 Memory Leak 有關。
    -NET Memory Leak- XslCompiledTransform and “leaked” dynamic assemblies – If broken it is, fix it
    -NET Memory Leak- XmlSerializing your way to a Memory Leak – If broken it is, fix it you should
  2. 有 75721 個物件等待 Finalization
  3. Process 中有超過 3 個 AppDomains

關於第三點,我在 AppDomain 統計表找到明顯異常,從 9/14 晚上 11 點起,每隔幾秒就冒出一個 Domain,載入 18 個組件,組件大小 74MB,數量高達 1022 個,數字似乎與第 1 點的 1023 個動態組件呼應。

將資料用 Excel 重排,確認 1022 個 AppDomain 的執行時間從 9/14 23:28:04 到 9/15 00:57:49。

一般 ASP.NET 程式很少會另建 AppDomain 跑程式,對照 IIS Log,很幸運是測試台又在冷門時段 Log 很乾淨,發現有個 RDLC 報表匯出 PDF 的排程從 23:28 左右開始執行,大約送出了 2500 次 Request,於 00:54:58 起出現 HTTP 500,時間相當吻合。(推測原因就是記憶體用盡)

依據文件(Expression Evaluation in Local Mode – Brian Hartman's Report Viewer Blog),RDLC 會在沙箱 AppDomain 中執行,而文末提到了 VS2010 版 ReportViewer 曾存在 AppDomain Leak Bug(但後來已修復),AppDomain 爆增極有可能是 RDLC 報表引起的。網站使用的版本 ReportViewer 2012 (ver 11),推測已無文章中所提  Bug,但我想起上回研究 RDLC 子報表效能時學到「子報表會另起 Instance 執行」,同事補上同一組程式在正式環境執行未傳出記憶體問題,進一步檢查發現問題主機的報表版本有使用子報表,而正式台已改用 List 奧步版,如此看來子報表極有可能就是嫌犯。將測試台 RDLC 也換成非子報表版本後有一段時間未再觀察記憶體爆表狀況,看來子報表是凶手的嫌疑極高,若之後有新發現,再做追蹤報導。

看完 DebugDiag Tools 在本案例強大的火力展示,讚嘆之餘,照慣例又到了呼口號時間:

DebugDiag Tools 好威呀!


Comments

Be the first to post a comment

Post a comment