一:背景

1. 讲故事

七月底的时候有位朋友在wx上找到我,说他的程序内存占用8G,托管才占用1.5G,询问剩下的内存哪里去了?截图如下:

从求助内容看,这位朋友真的太客气了,动不动就谈钱,真伤感情,如果有朋友一直关注我的分享,应该知道我一直都是免费分析dump,当然我的知识和经验也是有边界的,有些dump我也搞不定,不过我还是尽自己最大努力去寻找答案。

在这里我有必要说一下职场,在我的潜意识或者在我的团队中,这些很难搞的问题当然由技术领导去搞定,但我发现有好几起却不是这样的,技术经理搞不定转包下来,下面搞不定就让他另请高明。。。 有大佬可以分析下吗。

好了,闲话不多说,当务之急上windbg说话。

二: windbg 分析

1. 真的是非托管泄漏吗?

我在很多分析内存泄漏方面的文章都提到过,先要用二分法确定下是哪一部分的内存泄漏(托管还是非托管)。


0:000> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 387 7df2`11ac1000 ( 125.946 TB) 98.39%
<unknown> 2229 20c`a21bb000 ( 2.049 TB) 99.75% 1.60%
Heap 1081 1`33914000 ( 4.806 GB) 0.23% 0.00%
Image 1674 0`0e4be000 ( 228.742 MB) 0.01% 0.00%
Stack 973 0`0a140000 ( 161.250 MB) 0.01% 0.00%
TEB 324 0`00288000 ( 2.531 MB) 0.00% 0.00%
Other 11 0`001d9000 ( 1.848 MB) 0.00% 0.00%
PEB 1 0`00001000 ( 4.000 kB) 0.00% 0.00% --- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_MAPPED 300 200`00f9e000 ( 2.000 TB) 97.35% 1.56%
MEM_PRIVATE 3869 d`dd7ed000 ( 55.461 GB) 2.64% 0.04%
MEM_IMAGE 2124 0`0fda4000 ( 253.641 MB) 0.01% 0.00% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 387 7df2`11ac1000 ( 125.946 TB) 98.39%
MEM_RESERVE 1763 20b`d9903000 ( 2.046 TB) 99.60% 1.60%
MEM_COMMIT 4530 2`14c2c000 ( 8.324 GB) 0.40% 0.01% 0:000> !eeheap -gc
Number of GC Heaps: 40
------------------------------
Heap Size: Size: 0x3322e60 (53620320) bytes.
------------------------------
GC Heap Size: Size: 0x603046b0 (1613776560) bytes.

!address -summary!eeheap -gc 两条命令看,确实如朋友所说:MEM_COMMIT=8.3G, GC Heap=1.5G, 我去,果然是难搞的非托管内存泄漏,既然是地狱模式,那就硬着头皮继续看吧,要想继续排查的话,首先得看 windows nt 堆。

2. 查看 windows nt堆

其实不管是托管的C#还是非托管的C,C++,它们分配内存最终都需要调用 Windows 的 VirtualAlloc,HeapAlloc API 到 windows nt 上,接下来的研究方向是如何查找这些 .net 看不到的 nt堆, 可以使用 windbg 的 !heap -s 命令。


0:000> !heap -s ************************************************************************************************************************
NT HEAP STATS BELOW
************************************************************************************************************************
LFH Key : 0x0e4dcfd61ab09dd9
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-------------------------------------------------------------------------------------
000001bacd190000 00000002 4841944 4810424 4840388 13556 2042 303 2 dd8 LFH
000001baccfb0000 00008000 64 4 64 2 1 1 0 0
000001bacd4d0000 00001002 8772 6748 7216 1045 191 4 0 38 LFH
External fragmentation 15 % (191 free blocks)
000001bacdf90000 00001002 2636 404 1080 33 3 2 0 0 LFH
000001bace620000 00001002 8772 4052 7216 3874 13 7 0 1f LFH
External fragmentation 95 % (13 free blocks)
000001bace610000 00001003 60 8 60 6 1 1 0 N/A
000001bace540000 00001002 1616 24 60 4 2 1 0 1 LFH
000001baceb50000 00001002 4680 1228 3124 504 99 3 0 0 LFH
External fragmentation 41 % (99 free blocks)
000001baceb20000 00041002 60 8 60 5 1 1 0 0
000001baceb10000 00041002 1616 68 60 4 3 1 0 0 LFH
000001c7738a0000 00001002 49336 19316 47780 8249 43 22 0 13b LFH
External fragmentation 42 % (43 free blocks)
000001c7753c0000 00001002 13712 8460 12156 968 29 6 0 1c LFH
External fragmentation 11 % (29 free blocks)
000001c7763f0000 00001002 8772 3944 7216 423 25 4 0 3f LFH
000001ba977c0000 00001002 1080 376 1080 365 3 2 0 0
-------------------------------------------------------------------------------------

从上面的信息可以看出,当前有 14个 heap,其中最大的一个heap占了 4.8G,为啥这个heap这么大? 接下来详细看下这个heap,可使用 !ext.heap -stat -h 000001bacd190000


0:000> !ext.heap -stat -h 000001bacd190000
heap @ 000001bacd190000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
20034 8eee - 11df90858 (96.44)
2ee0000 2 - 5dc0000 (1.98)
851 1c2b - ea419b (0.31)
2ac00 28 - 6ae000 (0.14)
27d8 268 - 5fdfc0 (0.13)
24000 28 - 5a0000 (0.12)
d51 564 - 47c8a4 (0.09)
10d1 3e7 - 419f97 (0.09)
fd1 415 - 409025 (0.09)
29d1 12f - 317e5f (0.07)
138 18b0 - 1e1680 (0.04)
12c 188b - 1cc2e4 (0.04)
1000 17e - 17e000 (0.03)
2000 8e - 11c000 (0.02)
200 899 - 113200 (0.02)
ad1 178 - fe2f8 (0.02)
478 367 - f3448 (0.02)
7c8 1b9 - d6788 (0.02)
1c038 7 - c4188 (0.02)
f520 c - b7d80 (0.02)

可能很多人看不懂上面的卦象,首先 busy表示那些最近分配还未释放的,从卦头看,size=20034 的 block 有 36590 个,总占用:11df90858 = 4797827160byte = 4.7G,接下来的疑问很显然了,这些 block 里面到底都是些什么??? 要想找到答案,把这 3w 多的 block 信息都显示出来,可以用命令: !ext.heap -flt s 20034


0:000> !ext.heap -flt s 20034
_HEAP @ 1bacd190000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
000001c771f2ad30 2004 0000 [00] 000001c771f2ad40 20034 - (busy)
000001c774a65160 2004 2004 [00] 000001c774a65170 20034 - (busy)
000001c774a851a0 2004 2004 [00] 000001c774a851b0 20034 - (busy)
000001c774aa51e0 2004 2004 [00] 000001c774aa51f0 20034 - (busy)
000001c774ac5220 2004 2004 [00] 000001c774ac5230 20034 - (busy)
000001c774ae5260 2004 2004 [00] 000001c774ae5270 20034 - (busy)
000001c774b052a0 2004 2004 [00] 000001c774b052b0 20034 - (busy)
000001c774b29320 2004 2004 [00] 000001c774b29330 20034 - (busy)
000001c774b49360 2004 2004 [00] 000001c774b49370 20034 - (busy)
000001c774b693a0 2004 2004 [00] 000001c774b693b0 20034 - (busy)
000001c774b893e0 2004 2004 [00] 000001c774b893f0 20034 - (busy)
unknown!noop
000001c774ba9420 2004 2004 [00] 000001c774ba9430 20034 - (busy)
...
...

block块信息太多,这里我就贴一部分上去,上面列的 HEAP_ENTRY 就是 block 的首地址,然后我通过 dc 一顿找,发现不少下面的输出。


0:000> dc 000001c774a65160 L 50
000001c7`74a65160 dddddddd 00000000 cada2944 0c85cc02 ........D)......
000001c7`74a65170 74a21070 000001c7 74a851b0 000001c7 p..t.....Q.t....
000001c7`74a65180 00000000 00000000 00000000 00000001 ................
000001c7`74a65190 00020000 00000000 0000007a fdfdfdfd ........z.......
000001c7`74a651a0 00c801aa 55028000 05040355 44b60706 .......UU......D
000001c7`74a651b0 693d6f55 69502c31 32693d6e 7361502c Uo=i1,Pin=i2,Pas
000001c7`74a651c0 726f7773 33733d64 6f72472c 693d7075 sword=s3,Group=i
000001c7`74a651d0 74532c34 54747261 3d656d69 452c3569 4,StartTime=i5,E
000001c7`74a651e0 6954646e 693d656d 75532c36 41726570 ndTime=i6,SuperA
000001c7`74a651f0 6f687475 657a6972 0a37693d 72657375 uthorize=i7.user
000001c7`74a65200 68747561 7a69726f 2c323d65 3d6e6950 authorize=2,Pin=
000001c7`74a65210 412c3169 6f687475 657a6972 656d6954 i1,AuthorizeTime
000001c7`74a65220 656e6f7a 693d6449 75412c32 726f6874 zoneId=i2,Author
000001c7`74a65230 44657a69 49726f6f 33693d64 6c6f680a izeDoorId=i3.hol
000001c7`74a65240 79616469 482c333d 64696c6f 693d7961 iday=3,Holiday=i
000001c7`74a65250 6f482c31 6164696c 70795479 32693d65 1,HolidayType=i2
000001c7`74a65260 6f6f4c2c 33693d70 6d69740a 6e6f7a65 ,Loop=i3.timezon
000001c7`74a65270 2c343d65 656d6954 656e6f7a 693d6449 e=4,TimezoneId=i
000001c7`74a65280 75532c31 6d69546e 693d3165 75532c32 1,SunTime1=i2,Su
000001c7`74a65290 6d69546e 693d3265 75532c33 6d69546e nTime2=i3,SunTim

说实话用dc一个一个找,真的太累,这里我就写一个简单的脚本,把前1w个block都dc出来看看内容咋样?


"use strict"; var index = 1; function initializeScript() { return [new host.apiVersionSupport(1, 7)]; }
function log(str) { host.diagnostics.debugLog(str + "\n"); }
function exec(str) { log("\n" + str); return host.namespace.Debugger.Utility.Control.ExecuteCommand(str); } function invokeScript() {
show_heap_s();
} function show_heap_s() { //get top 1
var output = exec("!heap -s").Skip(10).First(); var h_address = output.split(' ')[0]; show_max_blocksize(h_address);
} function show_max_blocksize(address) { var output = exec("!ext.heap -stat -h " + address).Skip(3).First(); var block_size = output.trim().split(' ')[0]; show_all_blocksize(block_size);
} function show_all_blocksize(blocksize) { var output = exec("!ext.heap -flt s " + blocksize).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:\\dumps\\winform-memory-leak\\file\\" + pageIndex + ".txt " + heap_entry_address + " L?0x500"; var output = exec(path); log("pageIndex=" + pageIndex);
}

脚本执行后,输出结果如下:

问了下朋友这些字符串大概是干嘛的? 为啥非托管中有这么多的string没有得到释放,朋友告诉我这个大概是门禁相关业务,是通过 plc 方式和 C# 进行交互,分析到这里我能提供的信息都已提供了,接下来就要和门禁业务方确认下如何进一步定位和改进了。

三:总结

貌似这是20篇dump案例分享中第一个聊到非托管泄露的问题,曾今我在B站上说只专注于分析.NET托管内存泄漏,看样子很难实现哈,确实 C# 和 lua,C++,COM,内嵌浏览器 的交互造成非托管内存泄漏的例子数不胜数哈

更多高质量干货:参见我的 GitHub: dotnetfly

记一次 .NET 某智慧水厂API 非托管内存泄漏分析的更多相关文章

  1. 记一次 .NET 某打印服务 非托管内存泄漏分析

    一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的程序出现了内存泄漏,能不能帮他看一下,这个问题还是比较经典的,加上好久没上非托管方面的东西了,这篇就和大家分享一下,话不多说,上 WinD ...

  2. 记一次 .NET 某桌面奇侠游戏 非托管内存泄漏分析

    一:背景 1. 讲故事 说实话,这篇dump我本来是不准备上一篇文章来解读的,但它有两点深深的感动了我. 无数次的听说用 Unity 可做游戏开发,但百闻不如一见. 游戏中有很多金庸武侠小说才有的名字 ...

  3. 记一次 .NET 某智能服装智造系统 内存泄漏分析

    一:背景 1. 讲故事 上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下: 朋友这段话已经说的非常言简意赅了,那就上 windbg 说话吧. 二:Windbg 分析 1. ...

  4. 记一次 .NET 某HIS系统后端服务 内存泄漏分析

    一:背景 1. 讲故事 前天那位 his 老哥又来找我了,上次因为CPU爆高的问题我给解决了,看样子对我挺信任的,这次另一个程序又遇到内存泄漏,希望我帮忙诊断下. 其实这位老哥技术还是很不错的,他既然 ...

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

    一:背景 1. 讲故事 上周四有位朋友加wx咨询他的程序内存存在一定程度的泄漏,并且无法被GC回收,最终机器内存耗尽,很尴尬. 沟通下来,这位朋友能力还是很不错的,也已经做了初步的dump分析,发现了 ...

  6. 记一次 .NET 某风控管理系统 内存泄漏分析

    一:背景 1. 讲故事 上个月中旬,星球里的一位朋友在微信找我,说他的程序跑着跑着内存会不断的缓慢增长并无法释放,寻求如何解决 ? 得,看样子星球还得好好弄!!! 不管怎么说,先上 windbg 说话 ...

  7. 记一次 WinDbg 分析 .NET 某工厂MES系统 内存泄漏分析

    一:背景 1. 讲故事 上个月有位朋友加微信求助,说他的程序跑着跑着就内存爆掉了,寻求如何解决,截图如下: 从聊天内容看,这位朋友压力还是蛮大的,话说这貌似是我分析的第三个 MES 系统了,看样子 . ...

  8. 记一次 .NET 某消防物联网 后台服务 内存泄漏分析

    一:背景 1. 讲故事 去年十月份有位朋友从微信找到我,说他的程序内存要炸掉了...截图如下: 时间有点久,图片都被清理了,不过有点讽刺的是,自己的程序本身就是做监控的,结果自己出了问题,太尴尬了 二 ...

  9. 记一次 .NET 某电厂Web系统 内存泄漏分析

    一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5 在内存管理方面做的也不够好,所以有必要给大家分享 ...

随机推荐

  1. 使用Spring Data JPA 访问 Mysql 数据库-配置项

    jpa操作数据库 注意:数据库采用的是本机数据库,下面是建表语句及初始化数据: SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------- ...

  2. Html中的canvas

    使用cancas完成袋鼠跳跃的界面,这个是怎么做得呀,我哭了呀

  3. 海量数据Excel报表利器——EasyExcel(开场篇)

    EasyExcel 简介篇 互联网的精髓就是共享,可以共享技术.共享经验.共享情感.共享快乐~ 很多年前就有这个想法了,从事IT行业时间也不短了,应该把自己工作和业余所学习的东西记录并分享出来,和有缘 ...

  4. 10年程序员的编程语言感受与Go的结缘

    因为小编最近在写两套教程,分别是Java语言基础教程和Go语言的基础教程,并且这 2 门语言,小编在实际的工作中也都有用过 而且 www.helloworld.net 社区的后台,就是用Go语言写的, ...

  5. kubernetes的网络代理模式

    在k8s中,如果想ping svc以及ip,发现无法ping通,使用测试环境为k8s 1.6,后来k8s升级到1.12版本,发现ping svc以及ip可以ping通,这里分析一下原因. 后来发现是由 ...

  6. 【Azure 应用程序见解】Application Insights Java Agent 3.1.0的使用实验,通过修改单个URL的采样率来减少请求及依赖项的数据采集

    问题描述 近日好消息,如果是一个Java Spring Cloud的项目,想使用Azure Applicaiton Insights来收集日志及一些应用程序见解.但是有不愿意集成SDK来修改代码或者配 ...

  7. Java Set HashSet

    import java.util.HashSet; import java.util.Set; /** Set存储特点:数据无序.不可重复 Set接口的实现类: HashSet:Set接口的主要实现类 ...

  8. Java:HttpPost 传输Json数据过长使用HttpServletRequest解析

    直接上代码 /** * 测试生成json数据 */ @Test public void synYxGoodsInfoTest() { try { String url = "http://1 ...

  9. Cent OS下安装JDK11

    自己云服务器以前装了个JDK1.7,最近发现出了jdk11,所以修改一下JDK版本: 我这里用的是Xshell和XFtp工具,下载地址:https://www.netsarang.com/downlo ...

  10. XCTF_MFC逆向

    讲道理这题有点脑洞,也可能我太菜了,首先对mfc就不太熟悉,不知道是个啥玩意,只能边看大佬的wp百度边做了,之后要恶补一下mfc的知识了. 题目一开始说flag在控件中,看到大佬都是用Mfcspy来找 ...