天天看点

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

前天那位 his 老哥又来找我了,上次因为CPU爆高的问题我给解决了,看样子对我挺信任的,这次另一个程序又遇到内存泄漏,希望我帮忙诊断下。

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

其实这位老哥技术还是很不错的,他既然能给我dump,那真的是遇到很棘手的疑难杂症了😂😂😂,我得做好心理准备😬😬😬,沟通下来大概就是程序的内存会缓慢膨胀,直到自毁,问题就是这么一个问题,接下来祭出我的看家工具 windbg。

我在之前很多篇文章中都说过,遇到这种内存泄漏,首先就要排查到底是 <code>托管堆</code> 还是 <code>非托管堆</code> 的问题 ?如果是后者,大多数情况只能举手投降,因为这里面水太深了。。。 别看那些案例用 <code>AllocHGlobal</code> 方法分配非托管内存,然后用 !heap 去找的小儿科,现实情况比这种要复杂的多。。。

接下来先用 <code>!address -summary</code> 看一下当前进程的提交内存。

从卦象上看, 进程提交内存 <code>MEM_COMMIT = 8.2G</code>, 然后我们看下托管堆大小,使用 <code>!eeheap -gc</code> 命令。

从最后一行可以看出,当前的GC堆 <code>Size= 3442001168 /1024/1024/1024 =3.2G</code>,也就是说大概: <code>8.2G - 3.2G = 5G</code> 的内存丢掉了。。。尼玛,典型的 <code>非托管内存泄漏</code>,真的是哪壶不开提哪壶,这下可能真的要栽了。。。

除了 GC 堆,进程里面还有一个叫做 loader 堆,这里面东西就多了,有高频堆,低频堆,Stub堆,JIT堆 等等,存放着和 AppDomain,Module,方法描述符,方法表,EEClass 等相关信息,从经验来说,这个 loader 堆是考察 <code>非托管泄漏</code> 优先考虑的地方,要想查看,可使用 <code>!eeheap -loader</code> 命令。

这命令不输还好,一输吓一跳,windbg 界面刷了好几分钟才停下来。。。 从输出中可以得到两点信息:

loader堆 总共占用: <code>3237711872 /1024/1024/1024 = 3.01G</code>

有非常多的 module 产生,我估计有几万个。。。

为了满足好奇心,我决定写一个小脚本看看到底有多少个 module ???

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

我去,module居然有19w之多,难怪占用了 3 个多G,感觉离真相不远了,接下来的问题是这些module是什么,从哪里来???

要想寻找源头,大家可以仔细想一想, module 的嵌套关系应该是: <code>Module -&gt; Assembly -&gt; Appdomain</code> ,所以查 AppDomain 或许能给我们更多的信息,接下来使用 <code>!DumpDomain</code> 导出当前进程的所有应用程序域,又是刷刷刷的几分钟,哎。。。 截图如下:

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

从图中可以看出有大量的 <code>Dynamic</code> 类型的程序集,你肯定想问这是什么意思? 对,这就是代码动态创建的程序集,居然高达 19w 。。。接下来要解决的一个问题是:这些 Assembly 是怎么创建出来的???

老读者应该知道我是怎么从 module 中导出问题代码的,对,就是寻找 module 的 startaddress,这里我就挑选其中一个module:00007ffe2b1f2aa0。

我去,BaseAddress 居然没有地址,真倒霉,这也就是说该 module 你是无法导出的,想想也对,毕竟是动态生成的,可能写代码的人都搞不清楚module中是什么?难道真的就没有办法了吗? 可俗话说得好,天无绝人之路😅😅😅,在 <code>!dumpmodule</code> 命令中有一个 mt (methodtable) 参数,用来显示当前module中都有哪些类型,这就是重大线索。

可以看到module中定义了两个 <code>type</code>,都有其方法表地址,接下来通过 <code>mt</code> 来换取 <code>md</code> (方法描述符) 来得到最后module内容。

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

到这里终于就搞清楚了,原来这位老哥是利用 Castle 做了一个 AOP 的功能,应该是没有正确的使用 AOP ,导致生成了 <code>19w +</code> 的动态程序集,难怪最终会把内存给弄爆掉。。。 根子总算找到了,接下来如何去修改呢???

这下可把我难住了,毕竟我真的是没玩过 Castle 😥😥😥,不过老规矩,到 bing 上看看可有 <code>天涯沦落人</code>,嘿嘿,还真有 Castle AOP 导致内存泄漏的文章:Castle Windsor Interceptor memory leak ,解决办法也提供了,截图如下:

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

赶紧把这篇链接丢给老哥,我感觉也只能帮他到这里了,剩下的只能看造化。

真的是造化弄人,老哥以迅雷不及掩耳之势就给搞定了,当天晚上就已完成自测上线。

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

我赶紧追问老哥是怎么改的😁😁😁,老哥也不惜把源码放出来了,果然按照老外的建议将 <code>ProxyGenerator</code> 设置成 static 就搞定了。。。否则一个new一个assembly,再看看改之前的代码,截图如下:

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

搞定了这两个难啃的问题,感觉是不是要发一个小奖杯给我呢?😕😕😕

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

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