一:背景

1. 讲故事

前几天有位朋友加wx说他的程序遭遇了内存暴涨,求助如何分析?

和这位朋友聊下来,这个dump也是取自一个HIS系统,如朋友所说我这真的是和医院杠上了????????????,这样也好,给自己攒点资源????????????,好了,不扯了,上windbg说话。

二:windbg 分析

1. 托管还是非托管?

既然是内存暴涨,那就看看当前进程的 commit 内存有多大?


0:000> !address -summary--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                174     7ffe`baac0000 ( 127.995 TB)          100.00%
MEM_COMMIT                             1153        1`33bd3000 (   4.808 GB)  94.59%    0.00%
MEM_RESERVE                             221        0`1195d000 ( 281.363 MB)   5.41%    0.00%

可以看出大概占了 4.8G,接下来再看看托管堆内存。


0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00000207a4fc48c8
generation 1 starts at 0x00000207a3dc3138
generation 2 starts at 0x0000020697fc1000
ephemeral segment allocation context: none
------------------------------
GC Heap Size:            Size: 0x1241b3858 (4900730968) bytes.

从最后一行可以看出托管堆占用 4900730968/1024/1024/1024=4.5G,两个指标一比对,原来是托管内存出问题了,这下好办了。。。

2. 查看托管堆

既然内存是被托管堆吃掉了,那就看看托管堆上到底都有些什么东西???


0:000> !dumpheap -stat
Statistics:MT    Count    TotalSize Class Name
...
00007ffd00397b98  1065873    102323808 System.Data.DataRow
00000206978b8250  1507805    223310768      Free
00007ffd20d216b8  4668930    364025578 System.String
00007ffd20d22aa8      797    403971664 System.String[]
00007ffd20d193d0   406282   3399800382 System.Byte[]
Total 9442152 objects

不看不知道,一看吓一跳,System.Byte[] 差不多占用了 3.3 G 内存,也就是说 gc 堆差不多都被它吃掉了,根据经验肯定是有个什么大对象,那接下来怎么分析呢?除了用脚本对 byte[] 进行暴力分组统计之外,纯人肉还有其他的技巧吗? 当然有,可以用 !heapstat 观察下这些对象在托管堆上的代信息。


0:000> !heapstat
Heap             Gen0         Gen1         Gen2          LOH
Heap0         2252000     18880400   3968704192    910894376Free space:                                                 Percentage
Heap0           43128       770160    185203264     39849984SOH:  4% LOH:  4%

从图中可以看出,当前的大头在 Gen2 上,接下来可以用 eeheap -gc 去找 Gen2 的段地址区间,从而最小化的显示heap上内容。


0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00000207a4fc48c8
generation 1 starts at 0x00000207a3dc3138
generation 2 starts at 0x0000020697fc1000
ephemeral segment allocation context: nonesegment             begin         allocated              size
0000020697fc0000  0000020697fc1000  00000206a7fbec48  0xfffdc48(268426312)
00000206bbeb0000  00000206bbeb1000  00000206cbeaef50  0xfffdf50(268427088)
00000206ccc40000  00000206ccc41000  00000206dcc3f668  0xfffe668(268428904)
00000206dcc40000  00000206dcc41000  00000206ecc3f098  0xfffe098(268427416)
0000020680000000  0000020680001000  000002068ffff8c0  0xfffe8c0(268429504)
00000206ff4d0000  00000206ff4d1000  000002070f4cf588  0xfffe588(268428680)
000002070f4d0000  000002070f4d1000  000002071f4cf9f0  0xfffe9f0(268429808)
000002071f4d0000  000002071f4d1000  000002072f4cfef0  0xfffeef0(268431088)
000002072f4d0000  000002072f4d1000  000002073f4cf748  0xfffe748(268429128)
000002073f4d0000  000002073f4d1000  000002074f4ce900  0xfffd900(268425472)
00000207574d0000  00000207574d1000  00000207674cfe70  0xfffee70(268430960)
00000207674d0000  00000207674d1000  00000207774ceaf8  0xfffdaf8(268425976)
00000207774d0000  00000207774d1000  00000207874cf270  0xfffe270(268427888)
00000207874d0000  00000207874d1000  00000207974cf7a8  0xfffe7a8(268429224)
00000207974d0000  00000207974d1000  00000207a51ea5a8  0xdd195a8(231839144)

一般来说,第一个 segment 是给 gen0 + gen1 的,后续的 segment 就是 gen2,接下来我就选 segment:00000206dcc41000 - 00000206ecc3f098 ,然后使用 !dumpheap 导出该区间的所有对象。


0:000> !dumpheap -stat 00000206dcc41000 00000206ecc3f098
Statistics:MT    Count    TotalSize Class Name
00007ffd00397b98   191803     18413088 System.Data.DataRow
00007ffd20d216b8   662179     37834152 System.String
00007ffd20d193d0    23115    187896401 System.Byte[]

从这个内存段上看,Byte[] 有 2.3w 个,还不算多,全部dump出来看看有什么特征。


0:000> !dumpheap -mt 00007ffd20d193d0 00000206dcc41000 00000206ecc3f098Address               MT     Size
00000206dcc410e8 00007ffd20d193d0     8232
00000206dcc43588 00007ffd20d193d0     8232
00000206dcc45a48 00007ffd20d193d0     8232
00000206dcc47d78 00007ffd20d193d0     8232
00000206dcc4a028 00007ffd20d193d0     8232
00000206dcc4c4b0 00007ffd20d193d0     8232
00000206dcc4eb08 00007ffd20d193d0     8232
00000206dcc50e88 00007ffd20d193d0     8232
00000206dcc535b0 00007ffd20d193d0     8232
00000206dcc575d8 00007ffd20d193d0     8232
00000206dcc5a5a8 00007ffd20d193d0     8232
00000206dcc5cbf8 00007ffd20d193d0     8232
00000206dcc5eef8 00007ffd20d193d0     8232
00000206dcc611f8 00007ffd20d193d0     8232
00000206dcc634e8 00007ffd20d193d0     8232
00000206dcc657f0 00007ffd20d193d0     8232
00000206dcc67af8 00007ffd20d193d0     8232
00000206dcc69e00 00007ffd20d193d0     8232
...

我去,99% 都是 8232byte,原来都是些 8k 的byte数组,那到底谁在使用它,用 !gcroot 查一下引用根。


0:000> !gcroot 00000206dcc410e8
Thread 8c1c:rsi: ->  00000206983d5730 System.ServiceProcess.ServiceBase[]...->  000002069dcb6d38 OracleInternal.ConnectionPool.OraclePool...->  000002069dc949c0 OracleInternal.TTC.OraBufReader->  000002069dc94a70 System.Collections.Generic.List`1[[OracleInternal.Network.OraBuf, Oracle.ManagedDataAccess]]->  00000206ab8c2200 OracleInternal.Network.OraBuf[]->  00000206dcc41018 OracleInternal.Network.OraBuf->  00000206dcc410e8 System.Byte[]

从引用链来看,貌似是被 OracleInternal.Network.OraBuf[] 持有着,这就很疑惑了,难道是 Oracle Sdk 出的bug把内存给搞崩了?好奇心来了,看一下元素个数和size各是多少?


0:000> !do 00000206ab8c2200
Name:        OracleInternal.Network.OraBuf[]
MethodTable: 00007ffcc7833c68
EEClass:     00007ffd20757728
Size:        4194328(0x400018) bytes
Array:       Rank 1, Number of elements 524288, Type CLASS (Print Array)
Fields:
None0:000> !objsize 00000206ab8c2200
sizeof(00000206ab8c2200) = -1086824024 (0xbf3861a8) bytes (OracleInternal.Network.OraBuf[])

当前数组有 52w ,totalsize直接负数了????。

3. 寻找问题代码

知道现象之后,接下来用 ILSpy 把 Oracle SDK 反编译看看,最终一比对,如下图所示:

原来m_tempOBList是内存暴涨的罪魁祸首,这就很尴尬了,它为什么会暴涨?为什么不释放?由于我对 Oracle 也不熟悉,只能求助于神奇的 StackOverflow,我去,还真有天涯沦落人,Huge managed memory allocation when reading (iterating) data with DbDataReader

大概是说这种现象是 Oracle SDK 在读取 Clob 类型的字段有一个bug,解决办法也很简单,用完后就释放,详情参见如下图:

4. 寻找真相

既然帖子上是说读取 Clob 类型出的问题,那就把所有线程栈都调出来,看看此时的线程栈中是否有 Clob 的踪影?

从线程栈上看,代码是通过 ToDataTable 方法将 IDataReader 转成 DataTable,在转换过程中读取了大字段,自然就有了 GetCompleteClobData,也就是说完美命中帖子所说,为了让结论更准确,我就去挖一下当前的 DataReader 已经读了多少行了?


0:028> !clrstack -a
OS Thread Id: 0xbab0 (28)
000000e78ef7d520 00007ffd00724458 System.Data.DataTable.Load(System.Data.IDataReader, System.Data.LoadOption, System.Data.FillErrorEventHandler)PARAMETERS:this = <no data>reader (<CLR reg>) = 0x00000206a530ac20loadOption = <no data>errorHandler = <no data>
0:028> !do 0x00000206a530ac20
Name:        Oracle.ManagedDataAccess.Client.OracleDataReader
MethodTable: 00007ffcc7933b10
EEClass:     00007ffcc78efd30
Size:        256(0x100) bytes
File:        D:\xxx.dll
Fields:
00007ffd20d23e98  4000337       d0         System.Int32  1 instance          1061652 m_RowNumber

从 m_RowNumber 看,已经读取了 106w 行,一次性读取100w+的记录不常见,如果还有大字段的话,那也是????????了。

三:总结

综合来看这次事故是因为一次性读取含有大字段的百万级数据到DataTable引发,解决方案很简单,自己通过 for 读取 DataReader,在处理完 OracleClob 类型之后马上释放,参考帖子代码:


var item = oracleDataReader.GetOracleValue(columnIndex);if (item is OracleClob clob)
{if (clob != null){// use clob.Value ...clob.Close();}
}

END

工作中的你,是否已遇到 ...

1. CPU爆高

2. 内存暴涨

3. 资源泄漏

4. 崩溃死锁

5. 程序呆滞

等紧急事件,全公司都指望着你能解决...  危难时刻才能展现你的技术价值,作为专注于.NET高级调试的技术博主,欢迎微信搜索: 一线码农聊技术,免费协助你分析Dump文件,希望我能将你的踩坑经验分享给更多的人。

记一次 .NET 某三甲医院HIS系统 内存暴涨分析相关推荐

  1. 记一次 .NET 某WMS仓储打单系统 内存暴涨分析

    一:背景 1. 讲故事 七月中旬有一位朋友加wx求助,他的程序在生产上跑着跑着内存就飙起来了,貌似没有回头的趋势,询问如何解决,截图如下: 和这位朋友聊下来,感觉像是自己在小县城当了个小老板,规律的生 ...

  2. 记一次 .NET医疗布草API程序 内存暴涨分析

    一:背景 1. 讲故事 我在年前写过一篇关于CPU爆高的分析文章 再记一次 应用服务器 CPU 暴高事故分析 ,当时是给同济做项目升级,看过那篇文章的朋友应该知道,最后的结论是运维人员错误的将 IIS ...

  3. 记一次 .NET 医院CIS系统 内存溢出分析

    一:背景 1. 讲故事 前几天有位朋友加wx求助说他的程序最近总是出现内存溢出,很崩溃,如下图: 和这位朋友聊下来,发现他也是搞医疗的,哈哈,.NET 在医疗方面还是很有市场的???????????? ...

  4. 三甲医院his系统源码_三甲医院科研管理系统是什么,科研成果包括哪些

    对于三甲医院来说,做科研管理系统必不可少的是数据收集,有一个方便的数据收集管理软件能记科研效率提高很多,那就是三甲医院科研管理系统,首先,我们先了解一下三甲医院科研管理系统是什么,科研成果包括哪些,下 ...

  5. 三甲医院his系统源码_南京同行交流 | 三甲医院医疗大数据集成平台建设及打造可持续发展的数字底座架构设计...

    当前,我国大部分三甲医院普遍都在加速向智慧医疗转型,医疗数据加速互联互通.共享与分析,各种业务系统有望更好进行协同与运营,以及精细化服务能力得到进一步提升.医院之所以在当前加速智慧医疗的建设,主要是因 ...

  6. 大型三甲医院检验系统LIS源码 LIS系统源代码

    开发环境:.NET 4.0 WPF +VS2017或VS2019+SQL2016 一套功能完善,源码完整,可完美运行的医院LIS系统源码.源码中附带第三方示例代码,除Redis外源码中还有MongoD ...

  7. 记:四周实习项目--XX医院门诊系统

    职位:项目组长 接到的任务是:XX医院门诊系统,采用B/S设计 记得看到任务书的时候第一反应是有点蛋疼,据我所知,医院(我去过的)全是C/S设计的. 主要是就医疗行业来说,业务流程比较繁忙与复杂,处理 ...

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

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

  9. 记一次 .NET 某智慧物流WCS系统CPU爆高分析

    一:背景 1. 讲故事 哈哈,再次见到物流类软件,上个月有位朋友找到我,说他的程序出现了 CPU 爆高,让我帮忙看下什么原因,由于那段时间在苦心研究 C++,分析和经验分享也就懈怠了,今天就给大家安排 ...

最新文章

  1. Linux之软件卸载 apt-get
  2. 有6个候选人,100个选民,每个选民选择一个侯选人投票;从键盘输入每个选民选择的候选人名,统计并输出6个候选人的票数。java,c++实现
  3. WebAssembly 系列(五)为什么 WebAssembly 更快?
  4. 二叉排序树的中序遍历规律_看懂这篇文章,玩转二叉查找树
  5. 我的postfix过滤邮件、群发邮件问题 跟某云平台的故事
  6. keepalive 原理讲解
  7. 演示:两台交换机成环后的STP计算原则
  8. 怎样才算得上合格的程序员,教你一招
  9. nod32 linux升级方法,打造全自动的NOD32升级服务器
  10. fpga烧写bin文件_FPGA烧写程序
  11. 查看主机DNSserver
  12. oracle中update锁记录,oracle进行update时对行加锁的测试
  13. MySQL 常用函数一览
  14. Unity 制作360全景视频 全景图片流程
  15. 网站被劫持怎么办,怎么解决?
  16. Unity分屏显示效果
  17. 存储单位bit、byte、KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB、CB、XB
  18. 搜狗输入法这次栽大了!
  19. RabbitMQ消息队列,发送消息失败、消息持久化、消费者失败处理方法和发送消息
  20. Learning Better Lossless Compression Using Lossy Compression

热门文章

  1. 如何识别是三层交换机还是二层交换机
  2. c#开发-基础知识及有用技巧(一)
  3. php ip2long 32 64位,詳談php ip2long 出現負數的原因及解決方法
  4. 2018-2019-1 20165234 《信息安全系统设计基础》第四周学习总结
  5. Linux常用开发环境软件-Redis安装(docker环境下)
  6. Codeforces936C. Lock Puzzle
  7. 整理ASP.NET MVC 5各种错误请求[401,403,404,500]的拦截及自定义页面处理实例
  8. ligerUI的列头合并代码片段
  9. JAVA学习博客---2015.5
  10. cudaMalloc和cudaMallocPitch