4

非典型 Out Of Memory 茶包

 3 years ago
source link: https://blog.darkthread.net/blog/export-excel-loop-oom-issue/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

非典型 Out Of Memory 茶包

calendar.svg 2021-07-23 08:50 PM comment.svg 1 eye.svg 1,407

幫忙看了一個 Excel 批次匯出作業記憶體不足錯誤,是我之前沒見過的樣態,增廣見聞之餘順手記錄一下。

問題情境是有個能批次匯出 Excel 報表檔的 Windows Form 程式,負責查詢資料庫後以來源代碼為單位,一個來源匯成一個 Excel 檔案,程式以 VB.NET 撰寫 (哈,好久沒看到你了,近來可好?),呼叫第三方廠商元件產生 Excel 檔。近期因業務調整,來源代碼數量倍增,批次匯出作業開始發生記憶體不足錯誤。

報案人已完成初步分析跟測試,觀察到以下現象:

  1. 迴圈大約產出到第 50 個 Excel 檔時出現 Out Of Memory 錯誤,此時工作管理員顯示程式的記憶體用量約 800MB,離經驗中 32bit 程式的 1.6GB 上限還有段距離
  2. 檢查程式碼,Excel 輸出函式用的都是區域變數,理論上函式結束即會釋出,主要 Excel 元件也有確實呼叫 Close() 或 Dispose(),未發現可能造成 Memory Leak 的失誤
  3. 以 For 迴圈呼叫元件產生 Excel 過程,以工作管理員觀察記憶體變化,一開始記憶體還會升升降降維持在 50MB 上下,一段時間後工作管理員回報程序「沒有回應」,一旦出現,記憶體便只升不降一路飆上 800MB,接著 Excel 元件發生 Out Of Memory 錯誤
  4. 試著用 Try ... Catch 包住 Excel 產生程式,出錯時 MessageBox 顯示錯誤後允許執行下一筆。這時有個好現的狀況,按下 MessageBox 確認鈕關閉訊息視窗,記憶體用量會降回 50MB,迴圈可繼續跑匯出 Excel 報表,但之後還再出現「沒有回應」重演記憶體只增不減升到 800MB,觸發 Out Of Memory 錯誤
  5. 嘗試加入 GC.Collect() 強制回收記億體,但無任何改善
    註:官方文件 說得沒錯 - 「在幾乎所有情況下,您都不需要呼叫 GC.Collect(),因為垃圾收集行程會持續執行。這個方法主要用於獨特的情況和測試。」,Runtime 對記憶體狀況的掌握比我們精準,我至今還沒遇過需要用 GC.Collect() 解決問題的案例

歸納蒐集的線索,我推測第三方 Excel 元件可能存在 Memory Leak 問題,但只在特定狀況發生,而程序出現「沒有回應」似乎是觸發點。Try ... Catch 時關掉 MessageBox 會釋放記這點很有趣,我想不出二者的關聯性。

做了一些嘗試,在 For 迴圈中加入 Thread.Sleep(2000) 只能延緩而無法阻止 Out Of Memory 發生。

「沒有回應」應是 UI Thread 長時間忙於執行迴圈造成,原本想改成另開 Thread 執行,但 VB.NET 我寫起來超級卡,要先用 C# 想再逐行翻譯,再加上如此架構異動蠻多的,決定這招先保留,當成其他方法無效時的殺手鐧。

另一個思考方向是 - 若問題出在第三方 Excel 元件,升級或改換其他元件產出 Excel 也是條路,但這異動層面又更大了,整個程式要重測,只能當成最後的大絕。

把方向再拉回避免「沒有回應」上,既然另開 Thread 工程太大,老司機想起 VB6/VBA 裡有個 DoEvents(),以前會夾在迴圈中,暫時將控制權傳遞給作業系統處理完佇列中的事件(回應滑鼠、鍵盤動作,重繪 UI 視窗... 等等),如此可避免程式陷入「沒有回應」狀態及畫面凍結。但這是 VB6 時代的事,.NET 還有嗎? 幸好,還有 - Application.DoEvents()

最後試出的解法是在填入 Excel 儲存格的 For 迴圈中加入 Application.DoEvents(),防止「沒有回應」發生,記憶體維持在 50MB 左右不再無止境飆升,記憶體不足錯誤也自此消失。

由以上結果,我推測第三方 Excel 元件有 Bug (爬文查到有人反映類似問題),釋放記憶體動作受作業系統訊息佇列影響失效,當程式陷入「沒有回應」狀態時便會出現 Memory Leak。但為什麼只到 800MB 就出現 Out of Memory 錯誤,而不是經驗中的 1.6GB,這點對我仍是個謎,而在官方文件確實也找得到 800MB 的說法:Troubleshoot Out of Memory issues (System.OutOfMemoryException) in ASP.NET

Your process is using much memory (typically over 800 MB in a 32-bit environment.)

這部分又是個有趣的問題,擇日再另案調查。

and has 1 comment

Comments

# 2021-07-24 01:35 AM by Huang

可能是這段 Common Causes of High Memory When your worker process approaches 800MB in private bytes, your chances of seeing an OOM condition begin to increase simply because the chances of finding a large, contiguous piece of memory within the 2GB address space begin to decrease significantly. Therefore, you want to avoid these high memory conditions.

Post a comment

Comment
Name Captcha 36 + 14 =

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK