日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

记一次 .NET 某外贸Web站 内存泄漏分析

發布時間:2023/12/4 asp.net 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 记一次 .NET 某外贸Web站 内存泄漏分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一:背景

1. 講故事

上周四有位朋友加wx咨詢他的程序內存存在一定程度的泄漏,并且無法被GC回收,最終機器內存耗盡,很尷尬。

溝通下來,這位朋友能力還是很不錯的,也已經做了初步的dump分析,發現了托管堆上有 10w+ 的 byte[] 數組,并占用了大概 1.1G 的內存,在抽取幾個 byte[] 的 gcroot 后發現沒有引用,接下來就排查不下去了,雖然知道問題可能在 byte[],但苦于找不到證據。????????????

那既然這么信任的找到我,我得要做一個相對全面的輸出報告,不能辜負大家的信任哈,還是老規矩,上 windbg 說話。

二:windbg 分析

1. 排查泄漏源

看過我文章的老讀者應該知道,排查這種內存泄露的問題,首先要二分法找出到底是托管還是非托管出的問題,方便后續采取相應的應對措施。

接下來使用 !address -summary 看一下進程的提交內存。

||2:2:080>?!address?-summary---?Type?Summary?(for?busy)?------?RgnCount?-----------?Total?Size?--------?%ofBusy?%ofTotal MEM_PRIVATE?????????????????????????????573????????1`5c191000?(???5.439?GB)??95.19%????0.00% MEM_IMAGE??????????????????????????????1115????????0`0becf000?(?190.809?MB)???3.26%????0.00% MEM_MAPPED???????????????????????????????44????????0`05a62000?(??90.383?MB)???1.54%????0.00%---?State?Summary?----------------?RgnCount?-----------?Total?Size?--------?%ofBusy?%ofTotal MEM_FREE????????????????????????????????201?????7ffe`9252e000?(?127.994?TB)??????????100.00% MEM_COMMIT?????????????????????????????1477????????0`d439f000?(???3.316?GB)??58.04%????0.00% MEM_RESERVE?????????????????????????????255????????0`99723000?(???2.398?GB)??41.96%????0.00%

從卦象的 MEM_COMMIT 指標看:當前只有 3.3G 的內存占用,說實話,我一般都建議 5G+ 是做內存泄漏分析的最低門檻,畢竟內存越大,越容易分析,接下來看一下托管堆的內存占用。

||2:2:080>?!eeheap?-gc Number?of?GC?Heaps:?1 generation?0?starts?at?0x00000002b37c0c48 generation?1?starts?at?0x00000002b3781000 generation?2?starts?at?0x0000000000cc1000------------------------------ GC?Heap?Size:????????????Size:?0xbd322bb0?(3174181808)?bytes.

可以看到,當前托管堆占用 3174181808/1024/1024/1024= 2.95G,哈哈,看到這個數,心里一陣狂喜,托管堆上的問題,對我來說差不多就十拿九穩了。。。畢竟還沒有失手過,接下來趕緊排查一下托管堆,看下是哪里出的問題。

2. 查看托管堆

要想查看托管堆,可以使用 !dumpheap -stat 命令,下面我把 Top10 Size 給顯示出來。

||2:2:080>?!dumpheap?-stat Statistics:MT????Count????TotalSize?Class?Name 00007ffd7e130ab8???116201?????13014512?Newtonsoft.Json.Linq.JProperty 00007ffdd775e560????66176?????16411648?System.Data.SqlClient._SqlMetaData 00007ffddbcc9da8????68808?????17814644?System.Int32[] 00007ffddbcaf788????14140?????21568488?System.String[] 00007ffddac72958????50256?????22916736?System.Net.Sockets.SocketAsyncEventArgs 00007ffd7deb64b0??????369?????62115984?System.Collections.Generic.Dictionary`2+Entry[[System.Reflection.ICustomAttributeProvider,?mscorlib],[System.Type,?mscorlib]][] 00007ffddbcc8610?????8348????298313756?System.Char[] 00007ffddbcc74c0??1799807????489361500?System.String 000000000022e250???312151????855949918??????Free 00007ffddbccc768???109156???1135674368?System.Byte[]

從上面的輸出中可以看到,當前狀元是 Byte[],榜眼是 Free,探花是 String,這里還是有一些經驗之談的,深究 Byte[] 和 String 這種基礎類型,投入產出比是不高的,畢竟大量的復雜類型,它的內部結構都含有 String 和 Byte[],比如我相信 MemoryStream 內部肯定有 Byte[],對吧,所以暫且放下狀元和探花,看一下榜眼或者其他的復雜類型。

如果你的眼睛犀利,你會發現 Free 的個數有 31W+,你肯定想問這是什么意思?對,這表明當前托管堆上有 31W+ 的空閑塊,它的專業術語叫 碎片化,所以這條信息透露出了當前托管堆有相對嚴重的碎片化現象,接下來的問題就是為什么會這樣?大多數情況出現這種碎片化的原因在于托管堆上有很多的 pinned 對象,這種對象可以阻止 GC 在回收時對它的移動,長此以往就會造成托管堆的支離破碎,所以找出這種現象對解決泄漏問題有很大的幫助。

補充一下,這里可以借助 dotmemory ,紅色表示 pinned 對象,肉眼可見的大量的紅色間隔分布,最后的碎片率為 85% 。

接下來的問題是如何找到這些 pinned 對象,其實在 CLR 中有一張 GCHandles 表,里面就記錄了這些玩意。

3. 查看 GCHandles

要想找到所有的 pinned 對象,可以使用 !gchandles -stat 命令,簡化輸出如下:

||2:2:080>?!gchandles?-stat Statistics:MT????Count????TotalSize?Class?Name 00007ffddbcc88a0??????278????????26688?System.Threading.Thread 00007ffddbcb47a8?????1309???????209440?System.RuntimeType+RuntimeTypeCache 00007ffddbcc7b38??????100???????348384?System.Object[] 00007ffddbc94b60?????9359???????673848?System.Reflection.Emit.DynamicResolver 00007ffddb5b7b98????25369??????2841328?System.Threading.OverlappedData Total?36566?objectsHandles:Strong?Handles:???????174Pinned?Handles:???????15Async?Pinned?Handles:?25369Ref?Count?Handles:????1Weak?Long?Handles:????10681Weak?Short?Handles:???326

從卦象中可以看出,當前有一欄為:Async Pinned Handles: 25369 ,這表示當前有 2.5w 的異步操作過程中被pinned住的對象,這個指標就相當不正常了,而且可以看出與 2.5W 的System.Threading.OverlappedData 遙相呼應,有了這個思路,可以回過頭來看一下托管堆,是否有相對應的 2.5w 個類似封裝過異步操作的復雜類型對象?這里我再把 top10 Size 的托管堆列出來。

||2:2:080>?!dumpheap?-stat Statistics:MT????Count????TotalSize?Class?Name 00007ffd7e130ab8???116201?????13014512?Newtonsoft.Json.Linq.JProperty 00007ffdd775e560????66176?????16411648?System.Data.SqlClient._SqlMetaData 00007ffddbcc9da8????68808?????17814644?System.Int32[] 00007ffddbcaf788????14140?????21568488?System.String[] 00007ffddac72958????50256?????22916736?System.Net.Sockets.SocketAsyncEventArgs 00007ffd7deb64b0??????369?????62115984?System.Collections.Generic.Dictionary`2+Entry[[System.Reflection.ICustomAttributeProvider,?mscorlib],[System.Type,?mscorlib]][] 00007ffddbcc8610?????8348????298313756?System.Char[] 00007ffddbcc74c0??1799807????489361500?System.String 000000000022e250???312151????855949918??????Free 00007ffddbccc768???109156???1135674368?System.Byte[]

有了這種先入為主的思想,我想你肯定發現了托管堆上的這個 50256 的 System.Net.Sockets.SocketAsyncEventArgs,看樣子這回泄漏和 Socket 脫不了干系了,接下來可以查下這些 SocketAsyncEventArgs 到底被誰引用著?

4. 查看 SocketAsyncEventArgs 引用根

要想查看引用根,先從 SocketAsyncEventArgs 中導幾個 address 出來。

||2:2:080>?!dumpheap?-mt?00007ffddac72958?0?0000000001000000Address???????????????MT?????Size 0000000000cc9dc0?00007ffddac72958??????456????? 0000000000ccc0d8?00007ffddac72958??????456????? 0000000000ccc358?00007ffddac72958??????456????? 0000000000cce670?00007ffddac72958??????456????? 0000000000cce8f0?00007ffddac72958??????456????? 0000000000cd0c08?00007ffddac72958??????456????? 0000000000cd0e88?00007ffddac72958??????456????? 0000000000cd31a0?00007ffddac72958??????456????? 0000000000cd3420?00007ffddac72958??????456????? 0000000000cd5738?00007ffddac72958??????456????? 0000000000cd59b8?00007ffddac72958??????456????? 0000000000cd7cd0?00007ffddac72958??????456?????

然后查看第一個和第二個address的引用根。

||2:2:080>?!gcroot?0000000000cc9dc0 Thread?86e4:0000000018ecec20?00007ffd7dff06b4?xxxHttpServer.DaemonThread`2[[System.__Canon,?mscorlib],[System.__Canon,?mscorlib]].DaemonThreadStart()rbp+10:?0000000018ececb0->??000000000102e8c8?xxxHttpServer.DaemonThread`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]]->??00000000010313a8?xxxHttpServer.xxxHttpRequestServer`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]]->??000000000105b330?xxxHttpServer.HttpSocketTokenPool`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]]->??000000000105b348?System.Collections.Generic.Stack`1[[xxxHttpServer.HttpSocketToken`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]],?xxxHttpServer]]->??0000000010d36178?xxxHttpServer.HttpSocketToken`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]][]->??0000000008c93588?xxxHttpServer.HttpSocketToken`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]]->??0000000000cc9dc0?System.Net.Sockets.SocketAsyncEventArgs ||2:2:080>?!gcroot?0000000000ccc0d8 Thread?86e4:0000000018ecec20?00007ffd7dff06b4?xxxHttpServer.DaemonThread`2[[System.__Canon,?mscorlib],[System.__Canon,?mscorlib]].DaemonThreadStart()rbp+10:?0000000018ececb0->??000000000102e8c8?xxxHttpServer.DaemonThread`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]]->??00000000010313a8?xxxHttpServer.xxxHttpRequestServer`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]]->??000000000105b330?xxxHttpServer.HttpSocketTokenPool`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]]->??000000000105b348?System.Collections.Generic.Stack`1[[xxxHttpServer.HttpSocketToken`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]],?xxxHttpServer]]->??0000000010d36178?xxxHttpServer.HttpSocketToken`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]][]->??0000000000ccc080?xxxHttpServer.HttpSocketToken`2[[xxx.xxx,?xxx],[xxx.RequestInfo,?xxx]]->??0000000000ccc0d8?System.Net.Sockets.SocketAsyncEventArgs

從輸出信息看,貌似程序自己搭了一個 HttpServer,還搞了一個 HttpSocketTokenPool 池,好奇心來了,把這個類導出來看看怎么寫的?

5. 尋找問題代碼

還是老辦法,使用 !savemodule 導出問題代碼,然后使用 ILSpy 進行反編譯。

說實話,這個 pool 封裝的挺簡陋的,既然 SocketAsyncEventArgs 有 5W+,我猜測這個 m_pool 池中估計也得好幾萬,為了驗證思路,可以用 windbg 把它挖出來。

從圖中的size可以看出,這個 pool 有大概 2.5w 的 HttpSocket,這就說明這個所謂的 Socket Pool 其實并沒有封裝好。

三:總結

想自己封裝一個Pool,得要實現一些復雜的邏輯,而不能僅僅是一個 PUSH 和 POP 就完事了。。。所以優化方向也很明確,想辦法控制住這個 Pool,實現 Pool 該實現的效果。

END

工作中的你,是否已遇到 ...?

1. CPU爆高

2. 內存暴漲

3. 資源泄漏

4. 崩潰死鎖

5. 程序呆滯

等緊急事件,全公司都指望著你能解決...? 危難時刻才能展現你的技術價值,作為專注于.NET高級調試的技術博主,歡迎微信搜索: 一線碼農聊技術,免費協助你分析Dump文件,希望我能將你的踩坑經驗分享給更多的人。

總結

以上是生活随笔為你收集整理的记一次 .NET 某外贸Web站 内存泄漏分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。