OutOfMemoryException異常解析

dotNet源計劃發表於2021-10-19

一、概述

在國慶休假快結束的最後一天晚上接到了部門老大的電話,某省的服務會出現崩潰問題。需要趕緊修復,沒錯這次的主角依舊是上次的“遠古專案”沒有辦法同事都在休假沒有人能幫忙開電腦遠端只能叫車去公司。遠端連結上伺服器之後檢視日誌發現丟擲的堆疊異常資訊中包含了這樣一句話“OutOfMemoryException”,在A.dll中。

這個“遠古專案”大致情況如下:

  • 1.框架版本為.net framework 4
  • 2.程式碼結構混亂
  • 3.需要通過socket連線大量的物理裝置採集資料(1000臺左右)
  • 4.每小時採集一次,並由“遠古專案”接收並轉發

二、問題分析

(1)根據日誌定位問題

其實日誌中能給定的錯誤資訊有限,但是也有很大幫助起碼知道問題出在哪一塊。這時候直接找到A類庫檢視原始碼,這時候發現專案當中這一塊程式碼非常多大約1000行左右,這麼多程式碼到底哪一句出了問題不得而知。同時如果我想復現的話並不能有那麼多的裝置去模擬測試。這時候其實是有點暈的,這時候只能硬著頭皮把“OutOfMemoryException”這個異常拿去google一樣,結果發現是執行緒方面的記憶體溢位問題。那麼這時候縮小了檢視程式碼的範圍,就開始在程式碼中搜尋Thread物件的使用。檢視了半天果然有發現,看到了如下一段程式碼:

//...程式碼上下文
byte[] sendbytes = new byte[] { xxx };
var thread = new Thread((_) =>
{
   Send(sendbytes);
});
thread.Start();
//...程式碼上下文

這段程式碼存在的地方大概是,在所有裝置在每小時採集一次資料的時候會集中在某一個時間段裡大量的傳送資料若干次,且每臺裝置每次傳送資料的時候都會建立執行緒去傳送資料。看到這一段程式碼的時候我人都麻了。

(2)根據問題程式碼繼續分析

在程式開發中,建立執行緒的代價是非常高昂的。而且都集中在一個時間點上去頻繁建立執行緒這樣的程式碼肯定不行。這段程式碼極有可能就是引發這個異常的原因之一。分析到這裡突然想起之前看過的一本書,書中描述了這樣一段話:

“執行緒棧往往都很小。windows上預設情況下棧最大為1MB,並且大多數執行緒通常只使用很少的棧頁(stack page)。….”(書名:.NET效能分析)

基於以上理論增加了那段程式碼引起崩潰的可能性懷疑。那麼只能抱著試一試的心態繼續往下做。

三、解決方案

那麼發現了可能導致異常的程式碼如何去解決呢?這時候又有點頭疼了,因為我暫時能想到的解是:

Answer:利用生產消費者模式建立傳送佇列,然後開啟一個常駐的傳送執行緒慢慢發就可以了。

但是問題來了,“遠古專案”中結構太過混亂牽一髮動全身的連最簡單的這種思路實現都會有很多阻礙。而且框架版本過於低一些新語法特性也不能使用。

這個時候在想如果想解決這個問題應該要死扣住以下幾點:

  • 1.不能頻繁建立執行緒
  • 2.不能對程式碼有過大的改動
  • 3.對執行緒建立以及數量要有良好的控制
  • 4.不能考慮使用新語法特性

ThreadPool這個物件不是剛好滿足這個情況嗎,這時候將程式碼修改為:

byte[] sendbytes = new byte[] { 0 };
ThreadPool.QueueUserWorkItem(_=> 
{
   Send(sendbytes);
});

其實實現這一段程式碼之後,心裡依舊沒有解決問題的喜悅。因為根據執行緒池的原理,如果任務量過大的話還是會開闢預設執行緒數量以外的新執行緒。但是執行緒池對執行緒管理比較好,這樣的該的結果就能直接提交程式碼重新去伺服器上部署嗎?心裡還是沒有底我又做了如下測試:

//模擬在同一個時間點內大量開啟執行緒模擬多裝置傳送資料
for (int i = 0; i < 10000; i++)
{
   var thread = new Thread((_) =>
   {
       //模擬傳送資料耗時
       Thread.Sleep(1000);
   });
   thread.Start();
}

觀察VS記憶體監測變化。

 

OutOfMemoryException異常解析

 

發現在大量建立執行緒的時候CPU和記憶體佔用會陡增。那麼接下來我在試一試用執行緒池去執行這些操作會是一個什麼情況程式碼修改如下:

for (int i = 0; i < 100000; i++)
{
    ThreadPool.QueueUserWorkItem(_=> 
    {
       Thread.Sleep(1000);
    });
}

繼續觀察VS記憶體監測變化。

 

OutOfMemoryException異常解析

 

發現幾乎沒有任何波瀾,看到這個測試結果只能說感謝微軟實現了一個如此優秀的執行緒池。這個時候由於時間緊迫只能先改一版本拿到伺服器上去頂一陣肯定比上一個版本要好。

問題又來了如果再繼續出現問題怎麼繼續排查?下一次不一定能丟擲更有用的資訊。這個時候想到的解決方案如下:

  • 1.新增DUMP檔案輸出
  • 2.關鍵敏感地方加強日誌資訊詳細程度和適量try塊捕獲異常

到此耗時大約3小時左右,編譯好版本部署到伺服器上再做觀察。就這樣觀察了一個多星期沒有再次出現崩潰異常。其實分析下來,發現對這個問題發生原理可能還沒有玩明白需要繼續研究。

相關文章