前几天有位朋友找到我,说他的程序有内存泄露,让我帮忙排查一下,截图如下:,图片,说实话看到 32bit, 1.5G 这些关键词之后,职业敏感告诉我,他这个可能是虚拟地址紧张所致,不管怎么说,有了 Dump 就可以上马分析。,要看是不是虚拟地址紧张,可以用 !address -summary 观察下内存段统计信息,截图如下:,图片,我去,用 WinDbg Preview 尽然分析不了,在加载 ntdll 的过程中死掉了,如果你是我们调试训练营的朋友,应该会深深的有体会,我们分析的第一个dump就存在这个情况,这个加载不了其实就预示着一种非托管泄露,这里暂不剧透。,用 WinDbg Preview 分析不了怎么办呢?可以用 Windbg 的其他版本哈,比如 Windbg10, WinDbg6 等等,这里就采用 WinDbg10 X86 版本打开吧。,从卦中 MEM_COMMIT 的 %ofTotal= 42.18% 来看,提交内存占总的虚拟地址比重还不到一半,这说明我的猜测是错的,不存在虚拟地址紧张的情况,这里稍微提醒一下的是,这里不存在虚拟地址紧张是因为它开的是 Any CPU 模式,默认能吃到 4G 内存。,不管怎么说,现在被当头一棒,既然这条路走不通,那会是什么情况导致的呢?一般来说这个内存量我是不愿意分析的,但既然分析到这里也只能继续分析,接下来用 !eeheap -gc 观察下托管堆内存占用情况。,从卦中看当前托管堆也才 528M 和 提交内存 1.6G 相距甚远,所以这个 dump 大概率是存在非托管内存泄露,其实 !address -summary 中的 Heap 也能佐证,说到底就是 ntheap 泄露。,深挖 ntheap 我就不挖了,省的误入歧途,文章开头我说过 ntdll 无法加载的现象预示着一种非托管泄露,对 ,就是 GC 的加载堆泄露,加载堆是 CLR 用来映射 C# 程序集,模块,类型,方法等用途的一块私有内存,那怎么去洞察它呢?可以使用 !eeheap -loader 命令洞察。,虽然加载堆只统计到了 132M,但其中的 module 高达 2.3w 个,其实这里会有一些相关内存是加载堆之外无法统计到的,一般正常的程序不可能有这么多的module,所以这就是我们接下来突破的点,那怎么突破呢?最好的办法就是观察下这个 module 中到底有什么 type,使用 !dumpmodule 命令即可。,从模块中并没有看到类型的文字描述,那怎么办呢,我们随便抽一个 mt 看下这个 mt 下有什么方法,使用 !dumpmt 命令即可。,看到卦中的这些信息,我相信有很多朋友知道是怎么回事了,对,就是 Serialization 泄露,那它序列化什么类型呢 ? 从卦中看就是 xxx.Models.xxxBack 类,即 xmlSerializer.Serialize(xxx.Models.xxxBack) 的相关逻辑,接下来就需要逆向看下到底是哪里写的,结果发现是他的底层库封装的,有些方法有问题,有些没问题,真的是无语哈。,这是一个老生常谈的问题,如果你用 new XmlSerializer(o.GetType(), new XmlRootAttribute(rootName)); 模式的话,一定要缓存起来,否则就会泄露,只能说是微软造的一个大坑吧,多少人都踩上去了。,在我分析的真实dump案例中,见过 Castle ProxyGenerator 的泄露,也见过 CodeAnalysis.CSharp.Scripting 的泄露,还真没见过 XmlSerializer 的泄露,算是完美的补充了我的案例库!
© 版权声明
文章版权归作者所有,未经允许请勿转载。