记一次 .NET 某医院HIS系统 CPU爆高分析
一:背景
1. 讲故事
前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析?
和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还是没找对微软的大佬。。。
关于程序CPU爆高的问题,老读者应该知道我都写了好几篇了,基本上归为两类:
GC 触发
大量 lock 锁
少部分就是各种莫名其妙的问题了,无法一一列举 ,既然朋友找到我了,我得想办法给他解决,话不多聊,上 windbg。
二: windbg 分析
1. 查看同步块表
遇到这种问题,首查 同步块表
已经是我的惯性思维了,命令很简单 !syncblk
。
0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
-----------------------------
Total 20779
CCW 16
RCW 21
ComClassFactory 0
Free 16490
我去,扑了一个空,同步块表中啥也没有。。。 既然和锁没关系,那就看看线程吧,毕竟线程就是靠 CPU 养着的。
2. 查看线程
要想查看系统中的 托管线程
,可以使用 !t
命令, 线程比较多,稍微简化一下。
0:000> !t
ThreadCount: 135
UnstartedThread: 0
BackgroundThread: 132
PendingThread: 0
DeadThread: 1
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
34 1 25d4 000001ea28702130 28220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 0 Ukn
74 2 3ed0 000001ea286fa940 2b220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 0 MTA (Finalizer)
76 3 4a70 000001f4447d7810 102a220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 0 MTA (Threadpool Worker)
77 4 326c 000001f4447dbe60 21220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 0 Ukn
78 6 2dac 000001f4447d9750 1020220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 0 Ukn (Threadpool Worker)
79 7 1468 000001f444a2d6f0 21220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 1 Ukn (GC)
80 8 f0 000001f444a2cf20 21220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 0 Ukn
81 9 3118 000001f444a2f630 21220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 0 Ukn
先卖个关子,可有朋友看出这些线程有什么异样??? 对,线程 79
的最后一列有一个 Ukn (GC)
标记,我想你肯定好奇,这说明什么? 由于底层GC的模式有些变更,但不管怎么说,它在一定程度上告诉你,你的程序触发了GC,为了进一步验证,可以用 !t -special
看下 79 号线程到底属于什么类别以及更加详细的信息。
0:000> !t -special
ThreadCount: 135
UnstartedThread: 0
BackgroundThread: 132
PendingThread: 0
DeadThread: 1
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
34 1 25d4 000001ea28702130 28220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 0 Ukn
79 7 1468 000001f444a2d6f0 21220 Preemptive 0000000000000000:0000000000000000 000001ea286ee080 1 Ukn (GC)
OSID Special thread type
41 38d8 DbgHelper
42 1c88 GC
74 3ed0 Finalizer
75 402c ProfilingAPIAttach
76 4a70 Timer
79 1468 GC SuspendEE
从最后一行输出中可以看到, 79就是GC线程,后面还有一个奇怪的 SuspendEE
标记,你又好奇了,这又啥意思?
SuspendEE
=Suspend CLR Execution Engine (EE)
也就是说,79号线程把 CLR执行引擎
给冻结了,目的很简单,就是为了方便其他31个GC线程打理当前的 托管堆
,不过这老哥机器真,32core,也不知道哪家医院这么给力,补充一下,用 !cpuid
验证。
0:000> !cpuid
CP F/M/S Manufacturer MHz
0 6,62,4 <unavailable> 2600
1 6,62,4 <unavailable> 2600
2 6,62,4 <unavailable> 2600
3 6,62,4 <unavailable> 2600
xxx
31 6,62,4 <unavailable> 2600
既然预判到了是 GC 触发,下一步就可以把所有线程的托管和非托管堆栈全部调出来。
3. 查看各个线程栈
要想查看各个线程的托管和非托管栈很简单, 使用 !eestack
即可,然后可以检索关键词 WaitUntilGCComplete
来判断有多少线程在等待GC处理完毕。
从图中可以看出,当前有 40 个线程被阻塞了,真好,问题越来越清晰了,接下来再分析到底是哪个线程做了什么不该做的事,导致 GC 触发,同样也可以搜 try_allocate_more_space
来判断哪些线程正在分配空间。
我去,可以很明显的看出当前 170
和 187
号线程正在分配大对象 gc_heap::allocate_large_object
触发了 GC,本身大对象堆是一个令人生畏的东西,对它的回收清扫都是非常耗CPU资源的,这也和朋友说到的 1分钟左右CPU就下降了
的情况比较吻合。
4. 寻找祸首
现在关注点就在这两个线程上了,我看了下这两个线程栈都是同一处方法,所以这里就挑一个 187
线程来分析吧,可以用 !clrstack
看下它的托管栈。
0:187> !clrstack
OS Thread Id: 0x1ef0 (187)
Child SP IP Call Site
00000054ce631e30 00007ffc4021bde2 System.String.FillStringChecked(System.String, Int32, System.String)
00000054ce631e70 00007ffc402222a8 System.String.ConcatArray(System.String[], Int32)
00000054ce631ed0 00007ffc40223528 System.String.Concat(System.String[])
00000054ce631f40 00007ffbe6dbdafb BLL.xxx.xxx.GetRowString(System.String, Boolean, Boolean, System.String, System.String, System.String, System.String, System.String, System.String, Int32, System.String, System.String, System.String, System.String, System.String, System.String, Int32, Int32, System.String, System.Nullable`1, System.Nullable`1, System.String, System.Nullable`1, System.Nullable`1, System.Nullable`1, System.Nullable`1, System.String, System.String, System.String, System.String, System.String ByRef)
00000054ce65cf40 00007ffbe6ab3187 BLL.xxx.xxx.ExpZB(System.String, Boolean, Boolean, System.String, System.String, System.String, System.String, Int32, System.String, System.String, System.String, Int32, System.String, System.String, System.String, System.String, System.String, System.String, Int32, Int32, System.String, System.Nullable`1, System.Nullable`1, System.String, System.Nullable`1, System.Nullable`1, System.Nullable`1, System.Nullable`1, System.String, System.String, System.String, System.String)
从堆栈上看,貌似是使用了 System.String.Concat
拼接 string 所致,好家伙,string 拼接这么多年不知道被人抨击了多少次,还是有很多的人踩坑,为了进一步验证,可以使用 !clrstack -p + !da -details xxx
看看这个 System.String[] 到底是什么,简化如下:
0:187> !clrstack -p
OS Thread Id: 0x1ef0 (187)
00000054ce631e70 00007ffc402222a8 System.String.ConcatArray(System.String[], Int32)
PARAMETERS:
values (<CLR reg>) = 0x000001ea69e8d2f8
totalLength = <no data>
0:187> !da -details 0x000001ea69e8d2f8
Name: System.String[]
Size: 128(0x80) bytes
Array: Rank 1, Number of elements 13, Type CLASS
Element Methodtable: 00007ffc403d6948
[0] 000001f2391a83f0
Name: System.String
MethodTable: 00007ffc403d6948
EEClass: 00007ffc3fcd50e0
Size: 445950(0x6cdfe) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: xxxxx
从输出信息中可以看到,String[]
里面有 13 个元素,其中最大的一个 string 是 445950 bytes = 445k
,大于大对象的85k界定,所以这里的 Concat 就是症结所在,同样 170 线程也是如此,接下来我还得要解决的一个问题是:为什么会有如此大的字符串产生,代码里面到底做了什么??? 要想寻找答案,还得从dump中导出源码一探究竟。
5. 查看问题代码
要想分析问题代码,可以通过 !ip2md + !savemodule
导出 BLL.xxx.xxx.GetRowString 方法。
0:187> !ip2md 00007ffbe6dbdafb
MethodDesc: 00007ffbe5342118
Method Name: BLL.xxx.xxx.GetRowString(System.String, Boolean, Boolean, System.String, System.String, System.String, System.String, System.String, System.String, Int32, System.String, System.String, System.String, System.String, System.String, System.String, Int32, Int32, System.String, System.Nullable`1<Int32>, System.Nullable`1<Int32>, System.String, System.Nullable`1<Single>, System.Nullable`1<Single>, System.Nullable`1<Single>, System.Nullable`1<Single>, System.String, System.String, System.String, System.String, System.String ByRef)
Class: 00007ffbe52fe328
MethodTable: 00007ffbe53421d8
mdToken: 0000000006000096
Module: 00007ffbe471a890
0:187> !savemodule 00007ffbe471a890 E:\dumps\RowString.dll
3 sections in file
section 0 - VA=2000, VASize=f7fcc, FileAddr=200, FileSize=f8000
section 1 - VA=fa000, VASize=3bc, FileAddr=f8200, FileSize=400
section 2 - VA=fc000, VASize=c, FileAddr=f8600, FileSize=200
然后祭出 ILSpy
反编译这段代码。
好家伙,这写法真,无数的字符串拼接,我都感觉 gen
和 LOH
都来不及分配内存段了,真的是害死 GC 了。。。
三:总结
其实这是一个教科书式的问题,也有教科书式的解决方法,而且我看了下这个方法有 600 多行的代码,基本上都是做string拼接的事,最后说一下解决方案。
- 重构该方法,尽量用 StringBuilder 替代 String,将因这种 GC 触发的次数降到最低。
最后的小彩蛋,貌似这个分析结果和朋友的深度怀疑不约而同。。。
更多高质量干货:参见我的 GitHub: dotnetfly
记一次 .NET 某医院HIS系统 CPU爆高分析的更多相关文章
- 记一次 .NET 某智慧物流 WCS系统 CPU 爆高分析
一:背景 1. 讲故事 哈哈,再次见到物流类软件,上个月有位朋友找到我,说他的程序出现了 CPU 爆高,让我帮忙看下什么原因,由于那段时间在苦心研究 C++,分析和经验分享也就懈怠了,今天就给大家安排 ...
- 记一次 .NET游戏站程序的 CPU 爆高分析
一:背景 1. 讲故事 上个月有个老朋友找到我,说他的站点晚高峰 CPU 会突然爆高,发了两份 dump 文件过来,如下图: 又是经典的 CPU 爆高问题,到目前为止,对这种我还是有一些经验可循的. ...
- 记一次 .NET 某旅行社Web站 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序内存经常飙升,cpu 偶尔飙升,没找到原因,希望帮忙看一下. 可惜发过来的 dump 只有区区2G,能在这里面找到内存泄漏那真有两把刷子..., ...
- 记一次 .NET 某电商交易平台Web站 CPU爆高分析
一:背景 1. 讲故事 已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > ...
- 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析
一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...
- 记一次 .NET 某市附属医院 Web程序 偶发性CPU爆高分析
一:背景 1. 讲故事 这个月初,一位朋友加微信求助他的程序出现了 CPU 偶发性爆高,希望能有偿解决一下. 从描述看,这个问题应该困扰了很久,还是医院的朋友给力,开门就是 100块 红包 ,那既然是 ...
- 记一次 .NET 差旅管理后台 CPU 爆高分析
一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术 ...
- 记一次 .NET 某电子病历 CPU 爆高分析
一:背景 1.讲故事 前段时间有位朋友微信找到我,说他的程序出现了 CPU 爆高,帮忙看下程序到底出了什么情况?图就不上了,我们直接进入主题. 二:WinDbg 分析 1. CPU 真的爆高吗? 要确 ...
- 记一次 .NET 某三甲医院HIS系统 内存暴涨分析
一:背景 1. 讲故事 前几天有位朋友加wx说他的程序遭遇了内存暴涨,求助如何分析? 和这位朋友聊下来,这个dump也是取自一个HIS系统,如朋友所说我这真的是和医院杠上了,这样也好,给自己攒点资源, ...
随机推荐
- WorkSkill整理之 技能体系
- 2019HDU多校第七场 HDU6651 Final Exam
一.题目 Final Exam 二.分析 题目说的比较绕,总之一定要记住,$n$个题目都可以做,至少作对$k$到,但是做题目的人不知道每道题对应的分数. 作为出题人,如果他是田忌,肯定不会去在做题目的 ...
- Hznu_0j 1533 计算球体积(水)
题意:根据输入的半径值,计算球的体积: Input 输入数据有多组,每组占一行,每行包括一个实数,表示球的半径. Output 输出对应的球的体积,对于每组输入数据,输出一行,计算结果保留三位小数. ...
- P1060_开心的金明(JAVA语言)
思路 0/1背包问题 模板 //暴力出奇迹 题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:"你的房间需要购买哪些 ...
- 【博弈论】组合游戏及SG函数浅析
目录 预备知识 普通的Nim游戏 SG函数 预备知识 公平组合游戏(ICG) 若一个游戏满足: 由两名玩家交替行动: 游戏中任意时刻,合法操作集合只取决于这个局面本身: 若轮到某位选手时,若该选手无合 ...
- 图片的黑魔法——GitHub 热点速览 v.21.13
作者:HelloGitHub-小鱼干 图片的黑魔法并不是图片修复.旧照上色,而是将任意文件打包成图片的样子,上传到推特.看过去这张图片平平无奇,那么普通却深藏不露,工程师 DavidBuchanan ...
- IndentationError:unexpected indent”、“IndentationError:unindent does not match any outer indetation level”以及“IndentationError:expected an indented block Python常见错误
错误的使用缩进量 记住缩进增加只用在以:结束的语句之后,而之后必须恢复到之前的缩进格式. 经典错误,一定要注意缩进,尤其是在非界面化下环境的代码修改
- [2020年10月28日普级组]1406.SMRTFUN
S M R T F U N SMRTFUN SMRTFUN 题目描述 "又肥又温顺,又大又笨,他们看起来那么傻,而且也不有趣--" 这些牛想要证明,他们是既有趣,又聪明的.为了这样 ...
- Recoil Input 光标位置被重置到末尾的问题
考察如下代码,页面中有个输入框,通过 Recoil Atom 来存储输入的值. App.tsx function NameInput() { const [name, setName] = useRe ...
- Java(25-40)【数据类型转换、运算符、方法入门】
1.ASCII编码表 0--48 A--65 a--97 2. Unicode万国码 字符'中'为20013 3.算数运算符 double类型的加上int类型结果为double byte short ...