<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          記一次 .NET 某外貿(mào)Web站 內(nèi)存泄漏分析

          共 14459字,需瀏覽 29分鐘

           ·

          2021-05-30 07:08

          一:背景

          1. 講故事

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

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

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

          二:windbg 分析

          1. 排查泄漏源

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

          接下來使用 !address -summary 看一下進(jìn)程的提交內(nèi)存。


          ||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 指標(biāo)看:當(dāng)前只有 3.3G 的內(nèi)存占用,說實話,我一般都建議 5G+ 是做內(nèi)存泄漏分析的最低門檻,畢竟內(nèi)存越大,越容易分析,接下來看一下托管堆的內(nèi)存占用。


          ||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.

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

          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[]

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

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

          補(bǔ)充一下,這里可以借助 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 objects

          Handles:
              Strong Handles:       174
              Pinned Handles:       15
              Async Pinned Handles: 25369
              Ref Count Handles:    1
              Weak Long Handles:    10681
              Weak Short Handles:   326

          從卦象中可以看出,當(dāng)前有一欄為:Async Pinned Handles: 25369 ,這表示當(dāng)前有 2.5w 的異步操作過程中被pinned住的對象,這個指標(biāo)就相當(dāng)不正常了,而且可以看出與 2.5W 的System.Threading.OverlappedData 遙相呼應(yīng),有了這個思路,可以回過頭來看一下托管堆,是否有相對應(yīng)的 2.5w 個類似封裝過異步操作的復(fù)雜類型對象?這里我再把 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[]

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

          4. 查看 SocketAsyncEventArgs 引用根

          要想查看引用根,先從 SocketAsyncEventArgs 中導(dǎo)幾個 address 出來。


          ||2:2:080> !dumpheap -mt 00007ffddac72958 0 0000000001000000
                   Address               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+100000000018ececb0
                      ->  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+100000000018ececb0
                      ->  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 池,好奇心來了,把這個類導(dǎo)出來看看怎么寫的?

          5. 尋找問題代碼

          還是老辦法,使用 !savemodule 導(dǎo)出問題代碼,然后使用 ILSpy 進(jìn)行反編譯。

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

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

          三:總結(jié)

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







          回復(fù) 【關(guān)閉】學(xué)關(guān)
          回復(fù) 【實戰(zhàn)】獲取20套實戰(zhàn)源碼
          回復(fù) 【被刪】學(xué)
          回復(fù) 【訪客】學(xué)
          回復(fù) 【小程序】學(xué)獲取15套【入門+實戰(zhàn)+賺錢】小程序源碼
          回復(fù) 【python】學(xué)微獲取全套0基礎(chǔ)Python知識手冊
          回復(fù) 【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
          回復(fù) 【加群】加入dotnet微信交流群

          被網(wǎng)易云刷屏的人格顏色測試,我來總結(jié)下……爆火,點擊查看>>>


          【限時刪】劉*55頁ppt大瓜,比項*醒的還要精彩!



          瀏覽 44
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  免费在线看片黄在线观看 | 国产精品色8 | 夜夜艹天天艹 | 欧美草比视频 | 豆花成人版视频WWW18 |