记一次 .NET 某HIS系统后端服务 内存泄漏分析
一:背景
1. 讲故事
前天那位 his 老哥又来找我了,上次因为CPU爆高的问题我给解决了,看样子对我挺信任的,这次另一个程序又遇到内存泄漏,希望我帮忙诊断下。
其实这位老哥技术还是很不错的,他既然能给我dump,那真的是遇到很棘手的疑难杂症了,我得做好心理准备,沟通下来大概就是程序的内存会缓慢膨胀,直到自毁,问题就是这么一个问题,接下来祭出我的看家工具 windbg。
二: windbg 分析
1. 到底哪里泄漏了?
我在之前很多篇文章中都说过,遇到这种内存泄漏,首先就要排查到底是 托管堆
还是 非托管堆
的问题 ?如果是后者,大多数情况只能举手投降,因为这里面水太深了。。。 别看那些案例用 AllocHGlobal
方法分配非托管内存,然后用 !heap 去找的小儿科,现实情况比这种要复杂的多。。。
接下来先用 !address -summary
看一下当前进程的提交内存。
0:000> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 345 7dfd`ca3ca000 ( 125.991 TB) 98.43%
<unknown> 37399 201`54dbf000 ( 2.005 TB) 99.83% 1.57%
Heap 29887 0`d179b000 ( 3.273 GB) 0.16% 0.00%
Image 1312 0`0861b000 ( 134.105 MB) 0.01% 0.00%
Stack 228 0`06e40000 ( 110.250 MB) 0.01% 0.00%
Other 10 0`001d8000 ( 1.844 MB) 0.00% 0.00%
TEB 76 0`00098000 ( 608.000 kB) 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 352 200`00a40000 ( 2.000 TB) 99.57% 1.56%
MEM_PRIVATE 67249 2`2cbcb000 ( 8.699 GB) 0.42% 0.01%
MEM_IMAGE 1312 0`0861b000 ( 134.105 MB) 0.01% 0.00%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 345 7dfd`ca3ca000 ( 125.991 TB) 98.43%
MEM_RESERVE 11805 200`22ae8000 ( 2.001 TB) 99.60% 1.56%
MEM_COMMIT 57108 2`1313e000 ( 8.298 GB) 0.40% 0.01%
从卦象上看, 进程提交内存 MEM_COMMIT = 8.2G
, 然后我们看下托管堆大小,使用 !eeheap -gc
命令。
0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000027795928060
generation 1 starts at 0x000002779572F0D0
generation 2 starts at 0x000002763DCE1000
Total Size: Size: 0xcd28c510 (3442001168) bytes.
------------------------------
GC Heap Size: Size: 0xcd28c510 (3442001168) bytes.
从最后一行可以看出,当前的GC堆 Size= 3442001168 /1024/1024/1024 =3.2G
,也就是说大概: 8.2G - 3.2G = 5G
的内存丢掉了。。。尼玛,典型的 非托管内存泄漏
,真的是哪壶不开提哪壶,这下可能真的要栽了。。。
2. 寻找非托管内存泄漏
除了 GC 堆,进程里面还有一个叫做 loader 堆,这里面东西就多了,有高频堆,低频堆,Stub堆,JIT堆 等等,存放着和 AppDomain,Module,方法描述符,方法表,EEClass 等相关信息,从经验来说,这个 loader 堆是考察 非托管泄漏
优先考虑的地方,要想查看,可使用 !eeheap -loader
命令。
0:000> !eeheap -loader
...
Module 00007ffe2b1b6ca8: Size: 0x0 (0) bytes.
Module 00007ffe2b1b7e80: Size: 0x0 (0) bytes.
Module 00007ffe2b1b9058: Size: 0x0 (0) bytes.
Module 00007ffe2b1ba230: Size: 0x0 (0) bytes.
Module 00007ffe2b1bb408: Size: 0x0 (0) bytes.
Module 00007ffe2b1bc280: Size: 0x0 (0) bytes.
Module 00007ffe2b1bd458: Size: 0x0 (0) bytes.
Module 00007ffe2b1be630: Size: 0x0 (0) bytes.
Module 00007ffe2b1bf808: Size: 0x0 (0) bytes.
Module 00007ffe2b1f0a50: Size: 0x0 (0) bytes.
Module 00007ffe2b1f1c28: Size: 0x0 (0) bytes.
Module 00007ffe2b1f2aa0: Size: 0x0 (0) bytes.
Total size: Size: 0x0 (0) bytes.
--------------------------------------
Total LoaderHeap size: Size: 0xc0fb9000 (3237711872) bytes total, 0x5818000 (92372992) bytes wasted.
这命令不输还好,一输吓一跳,windbg 界面刷了好几分钟才停下来。。。 从输出中可以得到两点信息:
loader堆 总共占用:
3237711872 /1024/1024/1024 = 3.01G
有非常多的 module 产生,我估计有几万个。。。
为了满足好奇心,我决定写一个小脚本看看到底有多少个 module ???
我去,module居然有19w之多,难怪占用了 3 个多G,感觉离真相不远了,接下来的问题是这些module是什么,从哪里来???
3. 寻找 module 的源头
要想寻找源头,大家可以仔细想一想, module 的嵌套关系应该是: Module -> Assembly -> Appdomain
,所以查 AppDomain 或许能给我们更多的信息,接下来使用 !DumpDomain
导出当前进程的所有应用程序域,又是刷刷刷的几分钟,哎。。。 截图如下:
从图中可以看出有大量的 Dynamic
类型的程序集,你肯定想问这是什么意思? 对,这就是代码动态创建的程序集,居然高达 19w 。。。接下来要解决的一个问题是:这些 Assembly 是怎么创建出来的???
4. 导出 module 内容
老读者应该知道我是怎么从 module 中导出问题代码的,对,就是寻找 module 的 startaddress,这里我就挑选其中一个module:00007ffe2b1f2aa0。
2:2:152> !dumpmodule 00007ffe2b1f2aa0
Name: Unknown Module
Attributes: Reflection SupportsUpdateableMethods IsDynamic IsInMemory
Assembly: 000002776c1d8470
BaseAddress: 0000000000000000
PEFile: 000002776C1D8BF0
ModuleId: 00007FFE2B1F2EB8
ModuleIndex: 00000000000177CF
LoaderHeap: 0000000000000000
TypeDefToMethodTableMap: 00007FFE2B1EE8C0
TypeRefToMethodTableMap: 00007FFE2B1EE8E8
MethodDefToDescMap: 00007FFE2B1EE910
FieldDefToDescMap: 00007FFE2B1EE960
MemberRefToDescMap: 0000000000000000
FileReferencesMap: 00007FFE2B1EEA00
AssemblyReferencesMap: 00007FFE2B1EEA28
我去,BaseAddress 居然没有地址,真倒霉,这也就是说该 module 你是无法导出的,想想也对,毕竟是动态生成的,可能写代码的人都搞不清楚module中是什么?难道真的就没有办法了吗? 可俗话说得好,天无绝人之路,在 !dumpmodule
命令中有一个 mt (methodtable) 参数,用来显示当前module中都有哪些类型,这就是重大线索。
||2:2:152> !dumpmodule -mt 00007ffe2b1f2aa0
Name: Unknown Module
Attributes: Reflection SupportsUpdateableMethods IsDynamic IsInMemory
Assembly: 000002776c1d8470
Types defined in this module
MT TypeDef Name
------------------------------------------------------------------------------
00007ffe2b1f3168 0x02000002 <Unloaded Type>
00007ffe2b1f2f60 0x02000003 <Unloaded Type>
Types referenced in this module
MT TypeRef Name
------------------------------------------------------------------------------
00007ffdb9f70af0 0x02000001 System.Object
00007ffdbaed3730 0x02000002 Castle.DynamicProxy.IProxyTargetAccessor
00007ffdbaec8f98 0x02000003 Castle.DynamicProxy.ProxyGenerationOptions
00007ffdbaec7fe8 0x02000004 Castle.DynamicProxy.IInterceptor
可以看到module中定义了两个 type
,都有其方法表地址,接下来通过 mt
来换取 md
(方法描述符) 来得到最后module内容。
到这里终于就搞清楚了,原来这位老哥是利用 Castle 做了一个 AOP 的功能,应该是没有正确的使用 AOP ,导致生成了 19w +
的动态程序集,难怪最终会把内存给弄爆掉。。。 根子总算找到了,接下来如何去修改呢???
5. 修改 Castle AOP 问题代码
这下可把我难住了,毕竟我真的是没玩过 Castle ,不过老规矩,到 bing 上看看可有 天涯沦落人
,嘿嘿,还真有 Castle AOP 导致内存泄漏的文章:Castle Windsor Interceptor memory leak ,解决办法也提供了,截图如下:
赶紧把这篇链接丢给老哥,我感觉也只能帮他到这里了,剩下的只能看造化。
三:总结
真的是造化弄人,老哥以迅雷不及掩耳之势就给搞定了,当天晚上就已完成自测上线。
我赶紧追问老哥是怎么改的,老哥也不惜把源码放出来了,果然按照老外的建议将 ProxyGenerator
设置成 static 就搞定了。。。否则一个new一个assembly,再看看改之前的代码,截图如下:
搞定了这两个难啃的问题,感觉是不是要发一个小奖杯给我呢?
更多高质量干货:参见我的 GitHub: dotnetfly
记一次 .NET 某HIS系统后端服务 内存泄漏分析的更多相关文章
- 记一次 .NET 某招聘网后端服务 内存暴涨分析
一:背景 1. 讲故事 前段时间有位朋友wx找到我,说他的程序存在内存阶段性暴涨,寻求如何解决,和朋友沟通下来,他的内存平时大概是5G 左右,在某些时点附近会暴涨到 10G+, 画个图大概就是这样. ...
- 记一次 .NET 某消防物联网 后台服务 内存泄漏分析
一:背景 1. 讲故事 去年十月份有位朋友从微信找到我,说他的程序内存要炸掉了...截图如下: 时间有点久,图片都被清理了,不过有点讽刺的是,自己的程序本身就是做监控的,结果自己出了问题,太尴尬了 二 ...
- 记一次 WinDbg 分析 .NET 某工厂MES系统 内存泄漏分析
一:背景 1. 讲故事 上个月有位朋友加微信求助,说他的程序跑着跑着就内存爆掉了,寻求如何解决,截图如下: 从聊天内容看,这位朋友压力还是蛮大的,话说这貌似是我分析的第三个 MES 系统了,看样子 . ...
- 记一次 .NET 某智能服装智造系统 内存泄漏分析
一:背景 1. 讲故事 上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下: 朋友这段话已经说的非常言简意赅了,那就上 windbg 说话吧. 二:Windbg 分析 1. ...
- 记一次 .NET 某电厂Web系统 内存泄漏分析
一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5 在内存管理方面做的也不够好,所以有必要给大家分享 ...
- 记一次某制造业ERP系统 CPU打爆事故分析
一:背景 1.讲故事 前些天有位朋友微信找到我,说他的程序出现了CPU阶段性爆高,过了一会就下去了,咨询下这个爆高阶段程序内部到底发生了什么? 画个图大概是下面这样,你懂的. 按经验来说,这种情况一般 ...
- 记一次 .NET 某企业OA后端服务 卡死分析
一:背景 1.讲故事 前段时间有位朋友微信找到我,说他生产机器上的 Console 服务看起来像是卡死了,也不生成日志,对方也收不到我的httpclient请求,不知道程序出现什么情况了,特来寻求帮助 ...
- 记一次 .NET医疗布草API程序 内存暴涨分析
一:背景 1. 讲故事 我在年前写过一篇关于CPU爆高的分析文章 再记一次 应用服务器 CPU 暴高事故分析 ,当时是给同济做项目升级,看过那篇文章的朋友应该知道,最后的结论是运维人员错误的将 IIS ...
- 记一次 .NET 某外贸Web站 内存泄漏分析
一:背景 1. 讲故事 上周四有位朋友加wx咨询他的程序内存存在一定程度的泄漏,并且无法被GC回收,最终机器内存耗尽,很尴尬. 沟通下来,这位朋友能力还是很不错的,也已经做了初步的dump分析,发现了 ...
随机推荐
- WERTYU_键盘错位(JAVA语言)
package 第三章; import java.util.Scanner; /* * 把手放在键盘上时,稍不注意就会往右错一位.这样,输入Q会变成输入W,输入J会变成输入K等. 输 ...
- 《C++反汇编与逆向分析技术揭秘》--数据类型
浮点数类型 IEEE标准从逻辑上采用一个三元组{S, E, M}来表示一个数N,它规定基数为2,符号位S用0和1分别表示正和负,尾数M用原码表示,阶码E用移码表示.根据浮点数的规格化方法,尾数域的 ...
- canvas绘制图像轮廓效果
在2d图形可视化开发中,经常要绘制对象的选中效果. 一般来说,表达对象选中可以使用边框,轮廓或者发光的效果. 发光的效果,可以使用canvas的阴影功能,比较容易实现,此处不在赘述. 绘制边框 绘制 ...
- 第2课:操作系统网络配置【DevOps基础培训】
第2课:操作系统网络配置 --DevOps基础培训 1. DNS配置 1.1 什么是DNS? 域名系统(英文:Domain Name System,缩写:DNS)是互联网的一项服务.它作为将域名和IP ...
- [树形DP]加分二叉树
加 分 二 叉 树 加分二叉树 加分二叉树 题目描述 设一个n个节点的二叉树tree的中序遍历为(l,2,3,-,n),其中数字1,2,3,-,n为节点编号.每个节点都有一个分数(均为正整数),记第j ...
- HarmonyOS三方件开发指南(17)-BottomNavigationBar
目录: 1.引言 2.功能介绍 3.BottomNavigationBar使用指南 4.BottomNavigationBar开发指南 5.<HarmonyOS三方件开发指南>文章合集 引 ...
- 我成为 Microsofti Azure MVP 啦!(ps:不是美国职业篮球)
一,引言 今天是个高兴的日子,早上10点左右收到了来自微软的MVP的礼包的快递,对我来说,这是一件很值得纪念的日子.所以今天就水一篇心得感想吧!!! 我是年前2月9号开始申请MVP的,经历了差不多1个 ...
- Windows安装完ADFS后卸载ADFS清除ADFS数据库
ADFS卸载后不会卸载掉之前ADFS配置后留下来的数据库,所以如果有必要去删掉这个数据库的话需要找到对应的路径去将数据库删除掉. 具体路径为C:/windows/wid/data/adfsartifa ...
- BUAAOO第二单元代码分析
第一次作业 设计思路与感想 第一次作业是要求有捎带的电梯实现, 第一次作业是花费的时间比较长的一次,花费了很多的时间去思考架构的问题.起初是想要搞三个线程的:输入线程,调度器线程和电梯线程,想要搞一个 ...
- leetcode 刷题(数组篇)1题 两数之和(哈希表)
题目描述 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标. 你可以假设每种输入只会对应一个答案.但是,数组中同一个元 ...