记一次 .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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: .NET HIS系统源码是一个基于Microsoft .NET开发平台的医院信息管理系统,包含了医院的电子病历、药房管理、收费管理等多个模块。该系统的源码是由开发者所编写的程序代码,是系统的基础,具有重要的意义。 通过阅读.NET HIS系统源码,可以了解到整个系统的架构、功能设计和实现方法。其中包含了很多关键的技术和算法,如数据库设计、界面设计、数据交互、多线程编程等等。这些源码的编写涉及的领域极其广泛,需要开发者精通多种编程语言、技术和工具,并具有丰富的实战经验。 在实际开发过程中,通过借鉴.NET HIS系统源码可以快速掌握各种最佳实践和技术方案,提升开发效率和代码质量,减少实现过程中的错误和风险。 同时,开发者还可以从中寻找到具有实际应用场景的程序代码片段,运用到自己的开发项目中去,实现功能的快速开发。 综上所述,NET HIS系统源码是一个宝贵的资源,能够帮助开发者了解和学习医院信息管理系统的开发过程,并从中获取实践经验和技术知识。 ### 回答2: 对于.NET HIS系统源码的要求,这是一项非常专业和庞大的开发任务。HIS系统是医院信息管理系统,用于管理和监控所有医院相关的信息和流程。以下是根据通常的HIS系统要求和开发实践,大致说明为了开发.NET HIS系统源码需要考虑的一些重点内容。 首先,HIS系统需要具备基本的功能模块,如患者管理、挂号、医生排班、药品管理、医疗设备管理、费用结算等。这些功能模块应该能够完整地反映出医院的实际运行过程,并且能够满足医务人员和患者的需求。 其次,为了实现这些功能,HIS系统需要使用数据库来存储和管理各种数据。可以选择使用常见的关系型数据库,如SQL Server或MySQL,以及ORM技术来简化数据访问层的开发工作。此外,对于一些敏感的医疗数据,需要有相应的数据安全措施,如数据加密和权限控制等。 接下来,HIS系统需要设计用户友好的界面,以方便医务人员和患者进行操作。可以采用Web应用程序或Windows桌面应用程序的形式来开发用户界面,使其具备良好的交互性和响应速度。 此外,HIS系统需要考虑与第三方系统的集成,如与医疗设备、药品供应商、医保系统等的数据交换。可以利用现有的标准协议,如HL7或DICOM等,来实现数据的互通和共享。 最后,开发.NET HIS系统源码还需要关注系统的性能和稳定性。可以通过合理的架构设计、代码优化、缓存策略等手段来提高系统的性能,并使用日志录和异常处理机制来保证系统的稳定运行。 综上所述,开发.NET HIS系统源码需要综合考虑功能模块设计、数据库管理、用户界面开发、第三方系统集成以及系统性能和稳定性等方面的要求。这项任务需要具备深厚的软件开发经验和医疗行业知识,并且需要遵循相关的规范和法律法规,以确保系统的质量和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值