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

一:背景

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文件,希望我能将你的踩坑经验分享给更多的人。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301979.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

怎么把计算机隐藏文件显示出来,怎么把隐藏的文件夹显示出来

电脑中病毒后&#xff0c;比例exe病毒&#xff0c;很多文件夹都会被隐藏起来。那么怎么显示出来呢?以下是小编整理的隐藏的文件夹显示出来的步骤&#xff0c;希望可以帮助大家!1.在要设置显示隐藏文件夹的目录下→鼠标右键空白处→在右键菜单栏中选择“新建”→“文本文档”&a…

数字转字符函数_Excel之文本函数CONCATENATE/TEXT/LEFT/MID/RIGHT/FIND/LEN

本部分主要包CONCATENATE函数、LEFT函数、RIGHT函数、MID函数、LEN函数、FIND函数、SEARCH函数、SUBSTITUTE函数、REPLACE函数、TRIM函数、CLEAN函数、LOWER函数、UPPER函数、PROPER函数、EXCACT函数、TEXT函数、DOLLAR函数、VALUE函数、CHAR函数、CODE函数、T函数、FIXED函数和…

计算机表演赛新疆赛区,【图】第二十六届中国儿童青少年威盛中国芯HTC计算机表演赛“中国电信天翼杯”新疆赛区总决赛圆满结束_乌鲁木齐教育信息网...

新疆赛区自2006年首次参加中国儿童青少年计算机表演赛至今&#xff0c;已经连续成功举办了十一届赛事。第二十六届中国儿童青少年威盛中国芯HTC计算机表演赛“中国电信天翼杯”新疆赛区活动启动以来&#xff0c;新疆赛区组委会办公室积极组织专家通过网络直播形式对全疆各级信息…

css-6 df15,webpack 样式文件的代码分割(15)

获取全套webpack 4.x教程&#xff0c;请访问瓦力博客在上一小节&#xff0c;我们将开发环境和生产环境区分开来。这一小节&#xff0c;我们来操作如何将样式文件的代码分割。1.安装yarn add mini-css-extract-plugin此插件将CSS提取到单独的文件中。它为每个包含CSS的JS文件创建…

据说这是史上最牛逼的可视化神器

全世界只有3.14 % 的人关注了数据与算法之美我们粉丝里面有很多小伙伴喜欢玩数据分析&#xff0c;粗略估计有几千人。数据分析离不开数据可视化。比如我们前面介绍过pandas画图&#xff0c;matplotlib画图&#xff0c;pyecharts画图当然还有Tableau&#xff0c;今天推荐一款更牛…

excel统计分析——S-W正态性检验

参考资料&#xff1a; [1]马兴华,张晋昕.数值变量正态性检验常用方法的对比[J].循证医学,2014,14(02):123-128. 统计推断——正态性检验&#xff08;图形方法、偏度和峰度、统计&#xff08;拟合优度&#xff09;检验&#xff09;_sm.distributions.ecdf-CSDN博客 【统计学】…

怎能错过这个技术集市!转发有奖,惊喜连连!

点击蓝字 关注我们Learn. Connect. Code. 微软一年一度的Build大会即将拉开帷幕&#xff08;5月25-27日&#xff09;&#xff0c;今年的主题演讲&#xff0c;全球CEO Satya Nadella将会深入我们的工作&#xff0c;探讨开发者速度&#xff0c;智能云原生应用&#xff0c;以及在…

文件服务器搭建centos,centos8搭建ftp文件服务器

1.安装vsftpdyum install -y vsftpd2.执行以下命令设置FTP服务开机自启动systemctl enable vsftpd.service3.执行以下命令启动FTP服务systemctl start vsftpd.service4.执行以下命令创建ftp用户useradd ftpuser5.执行以下命令并按照提示设置“ftpuser"用户密码passwd ftpu…

将历史、数学、语文、地理、政治知识融会贯通的诀窍就是它

▲卢sir特别推荐点击上图进入玩酷屋在这个知识都是碎片化的时代&#xff0c;系统化的知识&#xff0c;显得弥足珍贵。今天小木就专门给大家推荐一套&#xff0c;将知识系统化整理&#xff0c;放到时间轴上展示的——《时间上的人物谱》里。这是一套特别的工具书&#xff0c;是一…

Nginx实战部署常用功能演示(超详细版),绝对给力~~~

前言上次分享了一些开发过程中常用的功能&#xff0c;但如果到真实环境中&#xff0c;其实还需要一些额外的配置&#xff0c;比如说跨域、缓存、配置SSL证书、高可用等&#xff0c;老规矩&#xff0c;还是挑几个平时比较常用的进行演示分享。上篇详见Nginx超详细常用功能演示&a…

程序员,为什么给你50万年薪,你还要搞死我公司?

全世界只有3.14 % 的人关注了数据与算法之美小卢,今天看到一条让人吐血的新闻。1月20日&#xff0c;深圳市某互联网游戏公司程序员燕某在游戏上线测试的当天&#xff0c;锁死了服务器与电脑&#xff0c;并恶意失踪&#xff0c;致公司损失惨重的事在网上曝光并引起热议。这到底是…

访问Web服务器时 使用的协议是,使用SOAP协议访问Web服务

SOAP是简单对象访问协议&#xff0c;它可看成是HTTP与XML的结合&#xff0c;其中XML部分是作为HTTP报文的实体主体部分。SOAP&#xff1a;简单对象访问协议&#xff0c;是一种轻量的、简单的、基于 XML 的协议&#xff0c;它被设计成在 WEB 上交换结构化的和固化的信息。 SOAP …

同时画多个饼图_手帐术 | 这个神奇饼图里,藏着时间管理的小秘诀

说起效率管理kk首先想到的就是时间饼了简直就是拖延症的救星啊&#xff01;&#xff01;&#xff01;不仅能帮我们规划日程而且相比于时间轴来说可视化更强今天kk就来介绍一下时间饼的使用方法~时间饼类型时间饼一共分为几种12h、15h、18h、24h等时间越长分的越细12小时12h适合…

【转】java io 总结(图)

2019独角兽企业重金招聘Python工程师标准>>> 转载于:https://my.oschina.net/dlpinghailinfeng/blog/91191

TensorFlow的各种应用,你晓得不?

近几年&#xff0c;AI 的重大进展对我们的日常生活产生了积极影响。随着Google发布了《Google AI 原则》( Google AI Principles )&#xff0c;它们为 AI 发展提供了一个框架。由于这一领域的发展非常迅速&#xff0c;一些原则的最佳实践&#xff0c;如 “ 避免制造或加强不公平…

6月开招|工业互联才是王道,最高可达50k!

就在昨天&#xff0c;工信部发布了《工业互联网十个典型应用场景和五个重点行业实践》&#xff0c;具体介绍10个典型场景及5个重点行业工业互联网的实际应用情况&#xff0c;可以看到国内工业互联网正在蓬勃发展&#xff0c;欣欣向荣&#xff0c;工业4.0的浪潮正在席卷整个行业…

2018 年最受欢迎的 Python 库,你都用过吗?

全世界只有3.14 % 的人关注了数据与算法之美前段时间&#xff0c;数据科学网站 KDnuggets 评选出了顶级 Python 库 Top15&#xff0c;领域横跨数据科学、数据可视化、深度学习和机器学习。推荐阅读《Python3.0科学计算指南》上图&#xff1a;根据 GitHub star 和贡献评选出的 2…

登录服务器修改数据库吗,如何修改服务器登录数据库 sa

如何修改服务器登录数据库 sa 内容精选换一换为确保华为云关系型数据库服务发挥出最优性能&#xff0c;用户可根据业务需求对用户创建的参数模板中的参数进行调整。您可以修改用户创建的数据库参数模板中的参数值&#xff0c;但不能更改默认数据库参数模板中的参数值。以下是您…

用NSubstitute来mock

Moq在.net中比较出名的mock框架&#xff0c;NSubstitute也是属于这类框架&#xff0c;使用方式也如出一辙&#xff0c;是通过实现子类来达到mock的效果。下面的测试方法都是按照 UnitTest的三板斧Arrange Act Assert来展开的&#xff0c;代码具体如下。using Microsoft.VisualB…

ubuntu安装mysql_Ubuntu18.04下安装MySQL

提示&#xff1a;以下操作均在root用户下进行&#xff0c;如在普通用户&#xff0c;请自行加上sudo&#xff01;# 查看有没有安装MySQL&#xff1a;dpkg -l | grep mysql# 安装MySQL&#xff1a;apt install mysql-server安装完成之后可以使用如下命令来检查是否安装成功&#…