记一次 .NET 某资讯论坛 CPU爆高分析
大概有11天没发文了,真的不是因为懒,本想前几天抽空写,不知道为啥最近求助的朋友比较多,一天都能拿到2-3个求助dump,晚上回来就是一顿分析,有点意思的是大多朋友自己都分析了几遍或者公司多年的牛皮藓问题,真的是心太累,不过也好,累那是走上坡路。
再回到正题,在一个月前,有位朋友wx找到我,他最近也在学习如何分析dump,可能经验不是很丰富,分析不下去了,截图如下:
虽然dump中的问题千奇百怪,但如果要汇成大类,还是有一些规律可循的,比如:gc频繁触发,大量锁 等等,详细汇总可以观摩我的星球,好了,既然分析不下去,那就来 windbg。
二:Windbg 分析
1. 查看CPU利用率
既然报过来说cpu过高,我得用数据验证下不是,老命令 !tp
。
0:057> !tp
CPU utilization: 100%
Worker Thread: Total: 51 Running: 30 Idle: 0 MaxLimit: 400 MinLimit: 4
Work Request in Queue: 11
Unknown Function: 6a0bbb30 Context: 1b4ca258
Unknown Function: 6a0bbb30 Context: 1b4ca618
Unknown Function: 6a0bbb30 Context: 1b4ca758
Unknown Function: 6a0bbb30 Context: 1cb88d60
Unknown Function: 6a0bbb30 Context: 1b4ca798
Unknown Function: 6a0bbb30 Context: 1b5a54d0
AsyncTimerCallbackCompletion TimerInfo@01f6e530
Unknown Function: 6a0bbb30 Context: 1b5a5a50
Unknown Function: 6a0bbb30 Context: 1cb892a0
Unknown Function: 6a0bbb30 Context: 1b4ca8d8
Unknown Function: 6a0bbb30 Context: 1cb88da0
--------------------------------------
Number of Timers: 1
--------------------------------------
Completion Port Thread:Total: 1 Free: 1 MaxFree: 8 CurrentLimit: 1 MaxLimit: 400 MinLimit: 4
我去,cpu打满了,对了,这里稍微提醒下, CPU utilization: 100%
指的是当前机器而不是程序,言外之意就是当机器的CPU 100% 时,并不一定是你所dump的程序造成的。
2. 是否为 GC 触发
面对这陌生的dump,先进行一些经验性排查,比如说是否为 GC 触发导致? 那怎么去验证这个假设呢? 为了让结果更准确一点,用 !t -special
导出线程列表,看看是否有 GC SuspendEE
字样。
0:057> !t -special
ThreadCount: 109
UnstartedThread: 0
BackgroundThread: 74
PendingThread: 0
DeadThread: 35
Hosted Runtime: no
OSID Special thread type
14 2594 DbgHelper
15 2be4 GC SuspendEE
16 dc4 GC
17 2404 GC
18 bb4 GC
19 2498 Finalizer
20 312c ProfilingAPIAttach
21 858 Timer
22 3a78 ADUnloadHelper
27 290c GC
28 2e24 GC
29 28b0 GC
30 1e64 GC
38 3b24 ThreadpoolWorker
...
90 2948 Gate
从输出看,尼玛果然有,那就表明确实是GC触发所致,如果你还不相信的话,可以参考下 coreclr 源码。
size_t
GCHeap::GarbageCollectGeneration(unsigned int gen, gc_reason reason)
{
dprintf (2, ("triggered a GC!"));
gc_heap::gc_started = TRUE;
{
init_sync_log_stats();
#ifndef MULTIPLE_HEAPS
cooperative_mode = gc_heap::enable_preemptive ();
dprintf (2, ("Suspending EE"));
BEGIN_TIMING(suspend_ee_during_log);
GCToEEInterface::SuspendEE(SUSPEND_FOR_GC);
END_TIMING(suspend_ee_during_log);
gc_heap::proceed_with_gc_p = gc_heap::should_proceed_with_gc();
gc_heap::disable_preemptive (cooperative_mode);
if (gc_heap::proceed_with_gc_p)
pGenGCHeap->settings.init_mechanisms();
else
gc_heap::update_collection_counts_for_no_gc();
#endif //!MULTIPLE_HEAPS
}
}
看到上面的 SuspendEE
的吗,它的全称就是 Suspend CLR Execute Engine
,接下来我们用 ~*e !dumpstack
看看哪一个线程触发了 CLR 中的 GarbageCollectGeneration
方法。
从图中可以看到是 53 号线程触发了,切到53号线程后换用 !clrstack
。
从线程栈看,程序做了一个 XXX.GetAll()
操作,一看这名字就蛮恐怖的,接下来我们再看看这块源码,到底做了什么操作,简化后的源码如下:
public static List<xxxx> GetAll()
{
string text = "xxxProperty_GetAll";
SqlDatabase val = new SqlDatabase(m_strConnectionString);
xxxPropertyTreeInfo xxxPropertyTreeInfo = null;
List<xxxPropertieInfo> list = new List<xxxPropertieInfo>();
DbCommand storedProcCommand = ((Database)val).GetStoredProcCommand(text);
using (IDataReader reader = ((Database)val).ExecuteReader(storedProcCommand))
{
while (DataBase.DataReaderMoveNext(reader))
{
xxxPropertyTreeInfo = new xxxPropertyTreeInfo();
xxxPropertyTreeInfo.LoadDataReader(reader);
list.Add(xxxPropertyTreeInfo);
}
}
return list;
}
public virtual void LoadDataReader(MethodBase method, object obj, IDataReader reader)
{
Hashtable hashtable = new Hashtable();
for (int i = 0; i < reader.FieldCount; i++)
{
hashtable.Add(reader.GetName(i).ToLower(), reader.GetValue(i));
}
Hashtable fieldProperties = GetFieldProperties(method, FieldType.DBField);
foreach (object key in fieldProperties.Keys)
{
PropertyInfo p = (PropertyInfo)fieldProperties[key];
object v = null;
if (hashtable.Contains(key))
{
v = hashtable[key];
}
if (v != null)
{
SetPropertieValue(ref obj, ref p, ref v);
}
}
}
从源码逻辑看:它执行了一个存储过程 xxxProperty_GetAll
, 然后把获取到数据的 reader 和 xxxPropertyTreeInfo 做了一个 mapping 映射,在映射的过程中触发了GC。
3. 是否为数据过大导致?
按照以往经验,应该是从数据库中获取了过多数据导致,那本次dump是不是呢?要想寻找答案, 先用 !dso
命令导出线程栈所有变量,然后用 !do xxx
查看 List<xxxPropertieInfo> list
的size,如下图所示:
从图中看,这个size并不大,那为什么会导致gc频繁触发呢?就算做了 反射
产生了很多的小对象,应该也没多大影响哈。。。 这又让我陷入了沉思。。。
4. 寻找问题根源
经过一顿查找,我发现了几个疑点。
- 有24个线程正在执行
XXX.GetALL()
方法。
- 托管堆中发现了 123 个 list,大的size 也有 1298,所以合计起来也不小哈。。。
0:053> !dumpheap -mt 1b9eadd0
Address MT Size
02572a9c 1b9eadd0 24
026eca58 1b9eadd0 24
0273d2a0 1b9eadd0 24
...
Statistics:
MT Count TotalSize Class Name
1b9eadd0 123 2952 System.Collections.Generic.List`1[[xxxPropertieInfo, xxx.Model]]
0:053> !DumpObj /d 28261894
Name: System.Collections.Generic.List`1[[xxxPropertieInfo, xxx.Model]]
MethodTable: 1b9eadd0
EEClass: 6e2c6f8c
Size: 24(0x18) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
6e6ff32c 4001891 4 System.__Canon[] 0 instance 23710638 _items
6e6f1bc0 4001892 c System.Int32 1 instance 1298 _size
6e6f1bc0 4001893 10 System.Int32 1 instance 1298 _version
6e6f0100 4001894 8 System.Object 0 instance 00000000 _syncRoot
6e6ff32c 4001895 4 System.__Canon[] 0 static <no information>
- 程序是 32bit
从内存地址就能判断当前程序是 32bit,这就意味着它的 segment 段会很小,也就意味着更多的GC回收。
三:总结
本次事故是由于:
- 多个线程频繁重复的调用 size=1298 的
GetALL()
方法。 - 使用低效的
反射方式
进行model映射,映射过程中产生了不少的小对象。 - 过小的 segment (32M)
三者结合造成GC频繁的触发。
改进方法也很简单。
- 最简单粗暴的方法: 将数据库的查询结果缓存一份。
- 稍微正规一点方法: 用
Dapper
替换低效的手工反射
,将程序改成 64bit 。
和朋友沟通了解,采用了第一种方法,终于把 CPU 摁下去了,一切都恢复了平静!
记一次 .NET 某资讯论坛 CPU爆高分析的更多相关文章
- 记一次 .NET 车联网云端服务 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序CPU经常飙满,没找到原因,希望帮忙看一下. 这些天连续接到几个cpu爆高的dump,都看烦了,希望后面再来几个其他方面的dump,从沟通上看, ...
- 记一次 .NET 差旅管理后台 CPU 爆高分析
一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术 ...
- 记一次 .NET 某电子病历 CPU 爆高分析
一:背景 1.讲故事 前段时间有位朋友微信找到我,说他的程序出现了 CPU 爆高,帮忙看下程序到底出了什么情况?图就不上了,我们直接进入主题. 二:WinDbg 分析 1. CPU 真的爆高吗? 要确 ...
- 记一次 .NET 某电商交易平台Web站 CPU爆高分析
一:背景 1. 讲故事 已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > ...
- 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析
一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...
- 记一次 .NET游戏站程序的 CPU 爆高分析
一:背景 1. 讲故事 上个月有个老朋友找到我,说他的站点晚高峰 CPU 会突然爆高,发了两份 dump 文件过来,如下图: 又是经典的 CPU 爆高问题,到目前为止,对这种我还是有一些经验可循的. ...
- 记一次 .NET 某医院HIS系统 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...
- 记一次 .NET 某旅行社Web站 CPU爆高分析
一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序内存经常飙升,cpu 偶尔飙升,没找到原因,希望帮忙看一下. 可惜发过来的 dump 只有区区2G,能在这里面找到内存泄漏那真有两把刷子..., ...
- 记一次 .NET 某市附属医院 Web程序 偶发性CPU爆高分析
一:背景 1. 讲故事 这个月初,一位朋友加微信求助他的程序出现了 CPU 偶发性爆高,希望能有偿解决一下. 从描述看,这个问题应该困扰了很久,还是医院的朋友给力,开门就是 100块 红包 ,那既然是 ...
随机推荐
- (5)java Spring Cloud+Spring boot+mybatis企业快速开发架构之SpringCloud-Spring Boot简介
Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是简化新 Spring 应用的初始搭建以及开发过程.该框架使用了特定的方式进行配置,从而使开发人员不再需要定义样板化的配置 ...
- MySQL查询结果集字符串操作之多行合并与单行分割
前言 我们在做项目写sql语句的时候,是否会遇到这样的场景,就是需要把查询出来的多列,按照字符串分割合并成一列显示,或者把存在数据库里面用逗号分隔的一列,查询分成多列呢,常见场景有,文章标签,需要吧查 ...
- 使用 VSCode 开发调试 STM32 单片机尝试
使用 VSCode 开发调试 STM32 单片机尝试 本文记录基于 Windows + DAP-Link 开发 STM32F103C8T6 的实践过程,其他操作系统或芯片应该也只是大同小异的问题. 注 ...
- html2canvas实现截取指定区域或iframe的区域
官网文档: http://html2canvas.hertzen.com/ 使用的是 jquery 3.2.1 html2canvas 1.0.0-rc.7 截取根据id的指定区域: var ca ...
- dedecms织梦调用指定文章id
{dede:arclist idlist="1349" channelid="1" addfields="date,city"} idli ...
- PHP文件包含漏洞小结
参考链接:https://chybeta.github.io/2017/10/08/php文件包含漏洞/ 四大漏洞函数 PHP文件包含漏洞主要由于四个函数引起的: include() include_ ...
- Chrome插件 - Modify Headers for Google Chrome(IP欺骗)
前景: 该篇随笔的由来:公司某项目(B/S架构)最近新加了一个后台日志功能,需要抓取到访问项目的主机IP,记录目标主机的操作,因此就需要不同得IP访问.并且项目专用浏览器是Chrome内核. Modi ...
- Object of type type is not JSON serializable
报这个错的原因是因为json.dumps函数发现字典里面有bytes类型的数据,无法编码.解决方法:将bytes类型的数据就把它转化成str类型. 定义dates[]后return JsonRespo ...
- 关于panic ,主协程的recover 是无法获取 子协程的panic 的
一.子协程的panic,只能在子协程中处理 下面的代码,main 函数 无法获取panic package main import ( "fmt" "time" ...
- 如何从阿里云Code升级至云效Codeup
如果你还在使用阿里云Code,不防看看如何从阿里云Code升级至云效Codeup,云效代码管理Codeup是阿里云出品的一款企业级代码管理平台,提供代码托管.代码评审.代码扫描.质量检测等功能,全方位 ...