记一次 .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: nonesegment             begin         allocated              size
000000df30fc0000  000000df30fc1000  000000df3178cae0  0x7cbae0(8174304)
Large object heap starts at 0x000000df40fc1000segment             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 : ENABLEDHeap     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 000000df2e680000heap @ 000000df2e680000
group-by: TOTSIZE max-display: 20size     #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 @ df2e680000HEAP_ENTRY Size Prev Flags            UserPtr UserSize - state000000df2e702dd0 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 未作合理的内存释放造成的非托管泄漏,具体怎么在代码层进行释放,这个要看朋友的造化了。

最后上一个小彩蛋,朋友太客气了。

没见过这么大的红包,我居然收了 ????????????,反手就给公司研发小伙伴一人一杯下午茶,在这里对朋友说一声感谢 ????????????

END

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

1. CPU爆高

2. 内存暴涨

3. 资源泄漏

4. 崩溃死锁

5. 程序呆滞

等紧急事件,全公司都指望着你能解决...  危难时刻才能展现你的技术价值,作为专注于.NET高级调试的技术博主,欢迎微信搜索: 一线码农聊技术,免费协助你分析Dump文件,希望我能将你的踩坑经验分享给更多的人。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/298221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

成年人的数学公式

1 成年人的数学公式2 唯一对我说过“别走好吗”的人3 主人发起疯来有多可怕4 别人家的狗看到主人有危险▼你家的……▼5 蜘蛛捕食瞬间6 蜡烛重燃&#xff08;冒的白烟是固态小颗粒的石蜡蒸汽&#xff0c;可燃&#xff09;你点的每个赞&#xff0c;我都认真当成了喜欢

Linux运维实战之DNS的高级配置(转发器、视图等)

上次博文我们具体配置了一台DNS服务器并实现了主辅之间的区域传送&#xff0c;本次博文我们来看看DNS的一些高级配置。 在进行DNS的高级配置之前&#xff0c;必须要理解DNS的原理&#xff08;参见http://sweetpotato.blog.51cto.com/533893/1596973&#xff09; 并且对DNS的基础…

史上最烧脑的学习方法,看完瞬间涨姿势!

▲ 点击查看 在美国&#xff0c;有一个天才儿童计划&#xff0c;选拔一批“天才”儿童进入天才班。这个计划是为了保证每个人接受平等教育机会的同时&#xff0c;也为精英成长提供合适的土壤。美国所谓的天才和精英是怎样的标准呢&#xff1f;进入天才班的儿童首先要通过一个叫…

WPF轮播图实现方式(二)

WPF开发者QQ群&#xff1a; 340500857 | 微信群 -> 进入公众号主页 加入组织欢迎转发、分享、点赞、在看&#xff0c;谢谢~。 01—效果预览02—代码如下一、EmphasizerCarousel.cs 代码如下using System; using System.Collections.Generic; using System.Collections.Obj…

实现链栈的各种基本运算的算法_LeetCode基础算法题第78篇:如何不用加减号实现两数的加法运算?...

一直很纠结算法的文章应该怎么写。最后觉得还是从最简单的level开始写吧&#xff0c;一开始就弄些重量级的&#xff0c;什么人工智能&#xff0c;机器学习的算法&#xff0c;还要有大量的数学以及优化的知识&#xff0c;小白们估计会很郁闷&#xff0c;当然我也不一定能做出来对…

Bootstrap在线编辑器简单分享

Bootstrap 已经使响应式网站开发变得简单很多。 但是如果你不必手动写全部代码&#xff0c;事情会如何呢&#xff1f; 如果你可以自由地选择你想要使用的Bootstrap 组件、并可以把它们拖拽到画布中&#xff0c;事情会如何呢&#xff1f;这就是Bootstrap 编辑器的用武之地。 在这…

爱因斯坦去世不到7个小时,大脑就被人偷走!还被切成240块,供变态医生把玩了几十年.........

全世界只有3.14 % 的人关注了爆炸吧知识你的智商说不定比爱因斯坦还高俗话说&#xff0c;人怕出名猪怕壮。作为世界闻名的大科学家&#xff0c;世人却更愿意把爱因斯坦的成就归功于“天赋异禀的脑子”&#xff0c;顺带着爱因斯坦其他的“身体零件”也都成了大家好奇的对象。爱因…

字符串池化,减少了三分之一的内存占用

字符串池化&#xff0c;减少重复实例&#xff0c;内存降低&#xff0c;一切就是这样的轻松愉快。开篇摘要 本文通过一个简单的业务场景&#xff0c;来描述如何通过字符串池化来减少内存中的重复字符串实例&#xff0c;从而减少内存的占用。在业务中&#xff0c;我们假设如下&am…

启动zookeeper_Giraph源码分析(一)—启动ZooKeeper服务

作者 | 白松Giraph介绍&#xff1a;Apache Giraph is an iterative graph processing system built for high scalability. For example, it is currently used at Facebook to analyze the social graph formed by users and their connections. Giraph originated as the ope…

【MFC】MFC中调用系统软键盘的几种方法

1.直接运行微软系统自带的虚拟键盘程序“osk.exe” 在普通MFC项目中可以调用ShellExecute或者WinExec方法来直接运行微软系统自带的虚拟键盘程序“osk.exe”&#xff0c;十分方便一句话就可以搞定。ShellExecute&#xff1a; ShellExecute(GetSafeHwnd(), NULL, _T("osk.e…

委派用户管理Hyper-v

在安装完成Hyper-v以后&#xff0c;默认情况下&#xff0c;只有管理员组的成员才有权限去管理Hyper-v&#xff0c;做一些诸如管理网络&#xff0c;管理虚拟机&#xff0c;管理磁盘等等的操作&#xff0c;普通用户没这个权限&#xff0c;从应用角度来说&#xff0c;如果公司所有…

这才是真正的蛙泳,还挺能蹦跶......

1 这才是真正的蛙泳▼2 给你们看看我平时藏起来的可爱▼3 沈腾这rap唱的跟闹着玩儿的似的▼4 能不能好好说话&#xff1f;▼5 建议再配套一张肛肠医院代金券▼6 这是我军培养出来的【特工鹅】吗&#xff1f;盯梢技能太强了▼7 钓鱼翻车现场▼你点的每个赞&#xff0c;我…

缓存机制 java_缓存机制:java中缓存的原理

外存&#xff1a;也就是我们经常说的(CDEF盘的大小)外储存器是指除计算机内存及CPU缓存以外的储存器&#xff0c;此类储存器一般断电后仍然能保存数据。常见的外存储器有硬盘、软盘、光盘、U盘等&#xff0c;一般的软件都是安装在外存中内存&#xff1a;内存是计算机中重要的部…

docker-compose 一键部署分布式配置中心Apollo

简介说起分布式肯定要想到分布式配置中心、分布式日志、分布式链路追踪等在分布式部署中业务往往有很多配置比如: 应用程序在启动和运行时需要读取一些配置信息&#xff0c;配置基本上伴随着应用程序的整个生命周期&#xff0c;比如&#xff1a;数据库连接参数、启动参数等,都需…

Sharepoint Designer 2007 Workflow

参考 http://office.microsoft.com/en-us/sharepoint-designer-help/collect-data-from-a-user-in-a-workflow-HA010209808.aspx 注意 这里的Review Document是一个Task List, 应该在site里面创建一个task list&#xff0c;可以用默认的Tasks.转载于:https://www.cnblogs.com/F…

uva705--slash maze

/*这道题我原本是将斜线迷宫扩大为原来的两倍&#xff0c;但是在这种情况下对于在斜的方向上的搜索会变的较容易出错&#xff0c;所以参考了别人的思路后将迷宫扩展为原来的3倍&#xff0c;这样就变成一般的迷宫问题了*/ 1 #include"iostream"2 #include"stdio.…

重磅公开!集14位名师教案的《最全高中数学解题思想方法汇编》

全世界只有3.14 % 的人关注了爆炸吧知识解数学题&#xff0c;除了掌握有关的数学知识之外&#xff0c;最好掌握一定的解题技巧甚至知道点解题思想。要知道高考试题的解答过程中蕴含着重要的数学思想方法&#xff0c;如果能有意识地在解题过程中加以运用&#xff0c;势必会取得很…

极域课堂管理系统软件如何取消控制_智慧物流自动化智能仓储管理架构分析

现阶段&#xff0c;智慧物流&#xff08;ztmapinfo.com.&#xff09;成为了时事热点&#xff0c;获得物流界广泛关注。许多 物流自动化系统、物流自动化设备都打出了智慧物流的幌子&#xff0c;在刚结束了的CeMAT亚洲物流展上&#xff0c;宣传展现智慧物流技术与商品的公司增加…

控制 Redis stream 的消息数量

控制 Redis stream 的消息数量IntroRedis Stream 是 Redis 5.0 引入的一个新的类型&#xff0c;之前我们介绍过使用 Redis Stream 来实现消息队列&#xff0c;可以参考之前的文章 使用 Redis Stream 实现消息队列&#xff0c;而 Stream 的消息会持久化地内存中&#xff0c;如果…

startindex 不能大于字符串长度_玩转云端丨redis的5种对象与8种数据结构之字符串对象(下)...

引言本文是对《redis设计与实现(第二版)》中数据结构与对象相关内容的整理与说明。本篇文章只对对象结构&#xff0c;1种对象——字符串对象。以及字符串对象所对应的两种编码——raw和embstr&#xff0c;进行了详细介绍。表达一些本人的想法与看法&#xff0c;也希望更多朋友一…