主頁 > .NET開發 > 記一次 .NET 某桌面奇俠游戲 非托管記憶體泄漏分析

記一次 .NET 某桌面奇俠游戲 非托管記憶體泄漏分析

2021-09-13 12:50:14 .NET開發

一:背景

1. 講故事

說實話,這篇dump我本來是不準備上一篇文章來解讀的,但它有兩點深深的感動了我,

  1. 無數次的聽說用 Unity 可做游戲開發,但百聞不如一見,

  2. 游戲中有很多金庸武俠小說才有的名字,太賞心悅目了,


000000df315978a8    0          3   玉骨扇
000000df31597cd8    0          3   云龍槍
000000df31596d88    0          3   陰風爪
000000df315967a8    0          4   雪魂絲鏈
000000df31596ad0    0          4   乙木神劍
000000df31596040    0          3   星耀冠
000000df31595328    0          3   烏金錘
...

所以說這么好的一個dump,我得給它留下點什么,

好了,話說回來這個緣分起于上個月有位朋友說它的程式虛擬記憶體占用非常大,咨詢如何解決,如下圖:

先甭管是什么問題,多抓幾個dump總不會錯的,幾經折騰后發了一個dump過來,

二: Windbg 分析

1. 到底是哪里的泄漏

分析記憶體方面的問題,還是那句話,一分為二看一下到底是哪一塊的記憶體泄漏(托管還是非托管),

先看一下行程總記憶體,使用 !address -summary 命令,


0:087> !address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free                                    458     7ffe`9e6a8000 ( 127.995 TB)          100.00%
Heap                                  48514        1`005fd000 (   4.006 GB)  72.51%    0.00%
<unknown>                              2504        0`2c6ad000 ( 710.676 MB)  12.56%    0.00%
Stack                                   504        0`2a000000 ( 672.000 MB)  11.88%    0.00%
Image                                   410        0`0a971000 ( 169.441 MB)   3.00%    0.00%
Other                                    18        0`001dc000 (   1.859 MB)   0.03%    0.00%
TEB                                     168        0`00150000 (   1.312 MB)   0.02%    0.00%
PEB                                       1        0`00001000 (   4.000 kB)   0.00%    0.00%

--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE                           51581        1`5130f000 (   5.269 GB)  95.36%    0.00%
MEM_IMAGE                               416        0`0aa6b000 ( 170.418 MB)   3.01%    0.00%
MEM_MAPPED                              122        0`05bce000 (  91.805 MB)   1.62%    0.00%

--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                458     7ffe`9e6a8000 ( 127.995 TB)          100.00%
MEM_COMMIT                            51465        1`1c741000 (   4.445 GB)  80.45%    0.00%
MEM_RESERVE                             654        0`45207000 (   1.080 GB)  19.55%    0.00%

從卦中得知 MEM_COMMIT=4.4G, 接下來再看下托管堆的記憶體占用,可以用命令 !eeheap -gc 命令,


0:087> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x000000df3118dc48
generation 1 starts at 0x000000df3118b098
generation 2 starts at 0x000000df30fc1000
ephemeral segment allocation context: none
         segment             begin         allocated              size
000000df30fc0000  000000df30fc1000  000000df3178cae0  0x7cbae0(8174304)
Large object heap starts at 0x000000df40fc1000
         segment             begin         allocated              size
000000df40fc0000  000000df40fc1000  000000df410637b8  0xa27b8(665528)
Total Size:              Size: 0x86e298 (8839832) bytes.
------------------------------
GC Heap Size:            Size: 0x86e298 (8839832) bytes.


從卦中得知 GC Heap Size= 8839832 Byte = 8M,我去,才這么點,有點開玩笑哈!!! ???????? ,很明顯這是非托管記憶體泄漏,既然方向已定,那就排查下非托管區域吧!

2. 探究非托管泄漏

按照經驗,尋找非托管泄漏,首先看下 loader 堆,很多程式往往是因為動態創建了太多程式集所致,比如經典的 Castle, XmlSerializer ,有興趣的朋友可以網上找下這方面的資料,這里使用 !eeheap -loader 命令查看,


0:087> !eeheap -loader

--------------------------------------
Jit code heap:
LoaderCodeHeap:    0000000000000000(0:0) Size: 0x0 (0) bytes.
Total size:        Size: 0x0 (0) bytes.
--------------------------------------
Module Thunk heaps:
Module 00007ffda5fa1000: Size: 0x0 (0) bytes.
Module 00007ffd485c4148: Size: 0x0 (0) bytes.
Module 00007ffda2631000: Size: 0x0 (0) bytes.
Module 00007ffda5331000: Size: 0x0 (0) bytes.
Module 00007ffdac621000: Size: 0x0 (0) bytes.
Module 00007ffdac4e1000: Size: 0x0 (0) bytes.
Module 00007ffda48b1000: Size: 0x0 (0) bytes.
Module 00007ffda1791000: Size: 0x0 (0) bytes.
Module 00007ffd487b1858: Size: 0x0 (0) bytes.
Total size:              Size: 0x0 (0) bytes.
--------------------------------------
Module Lookup Table heaps:
Module 00007ffda5fa1000: Size: 0x0 (0) bytes.
Module 00007ffd485c4148: Size: 0x0 (0) bytes.
Module 00007ffda2631000: Size: 0x0 (0) bytes.
Module 00007ffda5331000: Size: 0x0 (0) bytes.
Module 00007ffdac621000: Size: 0x0 (0) bytes.
Module 00007ffdac4e1000: Size: 0x0 (0) bytes.
Module 00007ffda48b1000: Size: 0x0 (0) bytes.
Module 00007ffda1791000: Size: 0x0 (0) bytes.
Module 00007ffd487b1858: Size: 0x0 (0) bytes.
Total size:              Size: 0x0 (0) bytes.
--------------------------------------
Total LoaderHeap size:   Size: 0x99000 (626688) bytes total, 0x2000 (8192) bytes wasted.
=======================================

從輸出看: Total LoaderHeap size= 626K,看樣子這次踏空了,那就進困難模式看看 Windows NT 堆,這里使用 !heap -s 命令,


0:087> !heap -s


************************************************************************************************************************
                                              NT HEAP STATS BELOW
************************************************************************************************************************
LFH Key                   : 0xb6c37b3e3a4a189e
Termination on corruption : ENABLED
          Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                            (k)     (k)    (k)     (k) length      blocks cont. heap 
-------------------------------------------------------------------------------------
000000df2e680000 00000002 4145084 4130108 4144304   1537   775   260    1      4   LFH
000000df2e1f0000 00008000      64      4     64      2     1     1    0      0      
000000df2e830000 00001002    1860    172   1080     15     5     2    0      0   LFH
000000df2ec80000 00001002    1860    236   1080      5     7     2    0      0   LFH
000000df309e0000 00001002      60      8     60      2     1     1    0      0      
000000df30bb0000 00041002      60      8     60      5     1     1    0      0      
000000df49bd0000 00001002     840     44     60      3     3     1    0      0   LFH
000000df49b20000 00041002    1860     96   1080      8     3     2    0      0   LFH
000000df30b40000 00001002      60     20     60      9     2     1    0      0      
000000df30b30000 00001002    1860    152   1080     11     8     2    0      0   LFH
000000df4bbb0000 00001002    3904   1292   3124     49     6     3    0      0   LFH
000000df89920000 00001002    1860    372   1080     14     7     2    0      0   LFH
000000df89be0000 00001006    1860    280   1080     23     2     2    0      0   LFH
000000df56f40000 00001006   32372  26204  31592   1434    21     6    0     6b   LFH
000000df56f10000 00001006    1860    176   1080     21     3     2    0      0   LFH
000000df89ac0000 00001006    3904   2160   3124     67     4     3    0     2e   LFH
-------------------------------------------------------------------------------------

從輸出資訊看:原來程式的記憶體都被 heap=000000df2e680000 給吸走了,那就深挖它吧,這里用 !heap -stat -h 000000df2e680000 命令看一下該heap的統計資訊,


0:087>  !ext.heap -stat -h 000000df2e680000
 heap @ 000000df2e680000
group-by: TOTSIZE max-display: 20
    size     #blocks     total     ( %) (percent of total busy bytes)
    2000 4cfd2 - 99fa4000  (68.76)
    58 9d7492 - 36201230  (24.17)
    12c 267e8 - 2d1c3e0  (1.26)
    21d1 c46 - 19f0b26  (0.72)
    4020 634 - 18dc680  (0.69)
    a0 26d00 - 1842000  (0.68)
    a 1d3ebb - 124734e  (0.51)
    10 f8d99 - f8d990  (0.43)
    6 16adae - 881214  (0.24)
    b b3508 - 7b4758  (0.22)
    7 115125 - 793803  (0.21)
    5 17b833 - 7698ff  (0.21)
    c 86027 - 6481d4  (0.18)
    9 afef9 - 62f6c1  (0.17)
    d 6a80f - 5688c3  (0.15)
    f 4f5a9 - 4a64e7  (0.13)
    e 54814 - 49f118  (0.13)
    8 8b092 - 458490  (0.12)
    13 3139b - 3a7481  (0.10)
    15 25d06 - 31a17e  (0.09)

從輸出資訊看,這塊heap主要是被 size=2000size=58 給填滿了,畢竟他們占比 68.76 + 24.17 = 92.93,所以挖他們很有必要,接下來用命令 !heap -flt s 2000 找出heap中所有的這些block的首地址,


0:087>  !ext.heap -flt s 2000
    _HEAP @ df2e680000
              HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state
        000000df2e702dd0 0201 0000  [00]   000000df2e702de0    02000 - (busy)
        000000df2e72c7e0 0201 0201  [00]   000000df2e72c7f0    02000 - (busy)
        000000df517400c0 0201 0201  [00]   000000df517400d0    02000 - (busy)
        000000df517420d0 0201 0201  [00]   000000df517420e0    02000 - (busy)
        000000df517440e0 0201 0201  [00]   000000df517440f0    02000 - (busy)
        000000df517460f0 0201 0201  [00]   000000df51746100    02000 - (busy)
        000000df51748100 0201 0201  [00]   000000df51748110    02000 - (busy)
        000000df5174a110 0201 0201  [00]   000000df5174a120    02000 - (busy)
        000000df5174c120 0201 0201  [00]   000000df5174c130    02000 - (busy)
        000000df5174e130 0201 0201  [00]   000000df5174e140    02000 - (busy)
        000000df51750140 0201 0201  [00]   000000df51750150    02000 - (busy)
        ...

上面的 HEAP_ENTRY 就是block的首地址,由于這樣的block大概有 4cfd2=31.5w 個,沒法一一列出,接下來就是用 dc 去觀察這些 block 的記憶體塊內容來發現其中規律,手工肯定太麻煩了,還是得借助下腳本,這里還是取前1w條查看,


function show_all_blocksize() {

    var output = exec("!ext.heap -flt s 58").Take(10000);
    for (var line of output) {

        var heap_entry_address = line.trim().split(' ')[0];

        if (heap_entry_address.indexOf("00") == -1) continue;

        show_heap_entry(heap_entry_address);
    }
}

function show_heap_entry(heap_entry_address) {

    var pageIndex = (index++);

    var path = ".writemem D:\\file\\"+ pageIndex + ".txt " + heap_entry_address + " L?0x58";

    var output = exec(path);

    log("pageIndex=" + pageIndex);
}

執行腳本生成到txt之后,截圖如下:

通過觀察發現,這個heap中有大量的用戶資訊,然后就拿這些資訊求證朋友了,

和朋友簡單溝通后,我也只能幫到這里,到此結案,

三:總結

本次事故的原因是由于 C# 呼叫 Lua 后,Lua 未作合理的記憶體釋放造成的非托管泄漏,具體怎么在代碼層進行釋放,這個要看朋友的造化了,

最后上一個小彩蛋,朋友太客氣了,

沒見過這么大的紅包,我居然收了 ??????,反手就給公司研發小伙伴一人一杯下午茶,在這里對朋友說一聲感謝 ??????

圖片名稱

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/299785.html

標籤:.NET技术

上一篇:記一次 .NET 某桌面奇俠游戲 非托管記憶體泄漏分析

下一篇:如何獲取列舉的描述資訊

標籤雲
其他(123570) Java(13369) Python(12731) C(7545) 區塊鏈(7372) JavaScript(7059) 基礎類(6313) AI(6244) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4120) MySQL(4012) Linux(3394) C語言(3288) C++語言(3117) Java相關(2746) 疑難問題(2699) 單片機工控(2479) Web開發(1951) 網絡通信(1793) 數據庫相關(1767) VB基礎類(1755) PHP(1727) 開發(1646) 系統維護與使用區(1617) .NETCore(1586) 基礎和管理(1579) JavaEE(1566) C++(1527) 專題技術討論區(1515) Windows客戶端使用(1484) HtmlCss(1466) ASP.NET(1428) Unity3D(1354) VCL組件開發及應用(1353) HTML(CSS)(1220) 其他技術討論專區(1200) WindowsServer(1192) .NET技术(1165) 交換及路由技術(1149) 語言基礎算法系統設計(1133) WindowsSDKAPI(1124) 界面(1088) JavaSE(1075) Qt(1074) VBA(1048) 新手樂園(1016) 其他開發語言(947) Go(907) HTML5(901) 新技術前沿(898) 硬件設計(872) 區塊鏈技術(860) 網絡編程(857) 非技術版(846) 一般軟件使用(839) 網絡協議與配置(835) Eclipse(790) Spark(750) 下載資源懸賞專區(743)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布