记一次 .NET医疗布草API程序 内存暴涨分析
一:背景
1. 講故事
我在年前寫過一篇關于CPU爆高的分析文章 再記一次 應用服務器 CPU 暴高事故分析 ,當時是給同濟做項目升級,看過那篇文章的朋友應該知道,最后的結論是運維人員錯誤的將 IIS 應用程序池設成 32bit 導致了事故的發生,這篇算是后續????????????,拖了好久才續上哈。
猶記得那些天老板天天找我們幾個人開會,大概老板是在傳導甲方給過來的壓力,人倒霉就是這樣,你說 CPU 爆高可怕吧,我硬是給摁下去了,好了,Memory 又爆高了,尼瑪我又給摁下去了,接著數據庫死鎖又來了,你能體會到這種壓力嗎????? 就像我在朋友圈發的那樣,程序再不跑我就要跑了。
所以有時候敬敬風水還是很有必要的,有點扯遠了哈,這篇我們來看看程序的內存暴漲如何去排查,為了讓你更有興趣,來一張運維發的內存監控圖。
從圖中可以看出,9點開始內存直線暴漲,絕對驚心動魄,要是我的小諾安這樣暴漲就好了????????????,接下來 windbg 說話。
二:windbg 分析
1. 說一下思路
內存暴漲了,最怕的就是 非托管層 出了問題,它的排查難度相比 托管層 要難10倍以上,所以遇到這類問題,先祈禱一下吧,gc堆也罷,loader堆也不怕,所以先看看是否 進程內存 ≈ gc堆內存 ?
2. 排查托管還是非托管
排查方式也很簡單,通過 !address -summary 看看進程的已提交內存,如下輸出:
0:000>?!address?-summary---?State?Summary?----------------?RgnCount?-----------?Total?Size?--------?%ofBusy?%ofTotal MEM_FREE????????????????????????????????261??????7fb`4b151000?(???7.982?TB)???????????99.77% MEM_RESERVE?????????????????????????????278????????2`6aafc000?(???9.667?GB)??51.35%????0.12% MEM_COMMIT?????????????????????????????2199????????2`4a3a3000?(???9.160?GB)??48.65%????0.11%可以看到已提交內存是 9.1G,接下來看下 gc 堆的大小,使用 !eeheap -gc 即可。
0:000>?!eeheap?-gc Number?of?GC?Heaps:?8 ------------------------------ Heap?0?(0000000002607740) generation?0?starts?at?0x00000001aaaa5500 generation?1?starts?at?0x00000001aa3fd070 generation?2?starts?at?0x0000000180021000 Heap?7?(0000000002713b40) generation?0?starts?at?0x000000053b3a2c28 generation?1?starts?at?0x000000053a3fa770 generation?2?starts?at?0x0000000500021000------------------------------ GC?Heap?Size:????????????Size:?0x1fdfe58c8?(8556271816)?bytes.從最后一行輸出中可以看到當前的占用是 8556271816 / 1024 /1024 /1024 = 7.9G ,太幸運了,果然是托管層出了問題,gc 上有大塊臟東西,這下有救了 ????????????。
3. 查看托管堆
知道托管堆出了問題后,接下來就可以用 !dumpheap -stat 去gc堆上翻一翻。
0:000>?!dumpheap?-stat Statistics:MT????Count????TotalSize?Class?Name 000007fef7b5c308????32867???????788808?System.DateTime 000007fef7b5e598????33049???????793176?System.Boolean 000007feec7301f8????30430??????1217200?System.Web.HttpResponseUnmanagedBufferElement 000007fef7b56020?????2931??????3130928?System.Object[] 000007fef7b580f8???219398??????5265552?System.Int32 000007fe9a0c5428????46423??????7799064?xxx.Laundry.Entities.V_InvoiceInfo 000007fef7b59638???164418??????7892064?System.Text.StringBuilder 000007fef7b56980???164713?????10059852?System.Char[] 000007fef7b5a278?????7351?????26037217?System.Byte[] 000007fe9a0d8758???????35?????28326856?xxx.Laundry.Entities.V_ClothesTagInfo[] 0000000002536f50????76837?????77016088??????Free 000007fe9a327ab0????46534????312964608?xxx.Laundry.Entities.V_InvoiceClothesInfo[] 000007fe9a0c4868??2068912????397231104?xxx.Laundry.Entities.V_ClothesTagInfo 000007fef7b55b70?98986851???3483764540?System.String 000007fe9a10ef80?23998759???3839801440?xxx.Laundry.Entities.V_InvoiceClothesInfo Total?126039641?objects我去,托管堆上的 xxx.Laundry.Entities.V_InvoiceClothesInfo 對象居然高達 2399w ,占了大概 3.6G,這還不算其附屬對象,對了,如果直接用 !dumpheap -mt xxx 輸出 address 的話,很難進行UI中止,所以這里有一個小技巧,用 range 來限定一下,如下代碼所示:
0:000>?!dumpheap?-mt?000007fe9a10ef80?0?0000000180027b30Address???????????????MT?????Size 0000000180027800?000007fe9a10ef80??????160????? 0000000180027910?000007fe9a10ef80??????160????? 0000000180027a20?000007fe9a10ef80??????160????? 0000000180027b30?000007fe9a10ef80??????160?????Statistics:MT????Count????TotalSize?Class?Name 000007fe9a10ef80????????4??????????640?xxx.Laundry.Entities.V_InvoiceClothesInfo Total?4?objects4. 查找引用根
接下來用 !gcroot 隨便找一個 address 查看它的引用鏈,看看它到底被誰引用著?
0:000>?!gcroot?0000000180027800 HandleTable:00000000013715e8?(pinned?handle)->?000000058003c038?System.Object[]->?00000004800238a0?System.Collections.Generic.List`1[[xxx.Laundry.APIService.Models.Common.BaseModel,?xxx.Laundry.APIService]]->?0000000317e01ae0?xxx.Laundry.APIService.Models.Common.BaseModel[]->?000000028010caf0?xxx.Laundry.APIService.Models.Common.BaseModel->?00000003014cbbd0?System.Collections.Generic.List`1[[xxx.Laundry.Entities.V_InvoiceInfo,?xxx.Laundry.Entities]]->?00000003014f3580?xxx.Laundry.Entities.V_InvoiceInfo[]->?00000003014cd7f0?xxx.Laundry.Entities.V_InvoiceInfo->?000000038cc49bf0?System.Collections.Generic.List`1[[xxx.Laundry.Entities.V_InvoiceClothesInfo,?xxx.Laundry.Entities]]->?000000038cc49c18?xxx.Laundry.Entities.V_InvoiceClothesInfo[]->?0000000180027800?xxx.Laundry.Entities.V_InvoiceClothesInfoFound?1?unique?roots?(run?'!GCRoot?-all'?to?see?all?roots).從輸出中可以看到,它貌似被一個 List<BaseModel> 所持有,哈哈,總算找到了,接下來就簡單了,直接用 !objsize 看一看它的 size 有多大?
0:000>?!objsize?00000004800238a0 sizeof(00000004800238a0)?=?-1972395312?(0x8a6fa2d0)?bytes?(System.Collections.Generic.List`1[[xxx.Laundry.APIService.Models.Common.BaseModel,?xxx.Laundry.APIService]])看到上面的 -1972395312 了嗎?我去,int 類型的 size 直接給爆掉了,果然是個大對象,就是你了。。。如果非要看大小也可以,寫一個腳本遍歷一下。
三:總結
知道是 List<BaseModel> 做的孽后,仔細閱讀了源碼才知道,原來是給用戶第一次數據全量同步的時候,服務端為了加速將數據緩存在 List<BaseModel> 這個靜態變量中,很遺憾的是并沒有在合適的時機進行釋放,造成了高峰期內存直線暴增,優化方案很簡單,就是修改業務邏輯咯,增加釋放內存的時機。
題外話
如果你遇到的是這種 Strong Handles 的靜態變量,也可以直接用可視化的 dotMemory 查看。
當然你要保證你有足夠的內存,畢竟也算是小10G的dump ????, 我的 16G 內存一下子就被吃掉了。。。
善于用 String 駐留池機制來優化,看看它的源碼定義吧。
對于那些重復度很高的string,用駐留池機制可以節省千百倍的內存空間,至于為什么可以優化,可參考我之前的文章:字符串太占內存了,我想了各種奇思淫巧對它進行壓縮?。
END
工作中的你,是否已遇到 ...?
1. CPU爆高
2. 內存暴漲
3. 資源泄漏
4. 崩潰死鎖
5. 程序呆滯
等緊急事件,全公司都指望著你能解決...? 危難時刻才能展現你的技術價值,作為專注于.NET高級調試的技術博主,歡迎微信搜索: 一線碼農聊技術,免費協助你分析Dump文件,希望我能將你的踩坑經驗分享給更多的人。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的记一次 .NET医疗布草API程序 内存暴涨分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人气TOP|当红炸子鸡「小明机器人」,出
- 下一篇: 撸码是需要直觉的