一:背景

1. 讲故事

前天有位朋友加微信求助他的程序出现了CPU爆高的问题,开局就是一个红包,把我吓懵了!

由于是南方小年,我在老家张罗处理起来不方便,没有第一时间帮他处理,朋友在第二天上午已经找出问题了,反馈说是一个 while(true) 导致的,这就有点意思了,在我分析的众多 CPU 爆高案例中,还真没遇到 while(true) 的情况,一直都抱有遗憾,真是运气好,年前赶上了,哈哈 。

接下来我们就用 windbg 一起来分析下吧。

二:Windbg 分析

1. 查看CPU占用率

一直关注我的朋友都知道,用 !tp 命令就可以了。


0:022> !tp
CPU utilization: 95 Unknown format characterUnknown format control characterWorker Thread: Total: 11 Running: 11 Idle: 0 MaxLimit: 32767 MinLimit: 4
Work Request in Queue: 0
--------------------------------------
Number of Timers: 3
--------------------------------------
Completion Port Thread:Total: 4 Free: 4 MaxFree: 8 CurrentLimit: 4 MaxLimit: 1000 MinLimit: 4

上面有一句 Unknown format characterUnknown format control characterWorker 显得不太和谐,其实就是 % 的意思,不知道为啥在 .NETCore 会出现这种编码问题 ,接下来我们用 !eeversion 看一下。


0:022> !eeversion
4.700.21.56803 (3.x runtime) free
4,700,21,56803 @Commit: 28bb6f994c28bc91f09bc0ddb5dcb51d0f066806
Workstation mode
In plan phase of garbage collection
SOS Version: 5.0.4.36902 retail build

从基本信息看,当前是 .netcore 3.x 版本,而且很明显看到当前 GC 处于计划阶段。那何为计划阶段呢?

2. 何为计划阶段

简而言之,计划阶段的GC需要决定当前的的托管堆是做简单的标记free操作,还是要做重量级的压缩操作,如果要压缩处理,还需要涉及到托管堆对象的重定位,这往往会耗费相当多的 CPU 时间片,接下来要探究的是什么导致了 GC 触发?

3. GC 触发原因

由于 GC 的触发往往是用户线程分配数据导致的,在GC触发的整个执行流中,其中有一环就是冻结 CLR执行引擎,也就是 SuspendEE,可以在 gc.app 中一探究竟。

为什么一定要提 SuspendEE 呢?是因为我可以通过 !t -special 找出那个 SuspendEE 的线程,这样准确度更高一点。


0:072> !t -special
ThreadCount: 54
UnstartedThread: 0
BackgroundThread: 40
PendingThread: 0
DeadThread: 1
Hosted Runtime: no OSID Special thread type
1 6328 DbgHelper
2 35c0 Finalizer
4 5aac Timer
5 38b0 ThreadpoolWorker
17 3530 ThreadpoolWorker
18 4484 ThreadpoolWorker
19 1e4c ThreadpoolWorker
21 6380 ThreadpoolWorker
44 5bc4 SuspendEE
52 8ac ThreadpoolWorker
54 4164 ThreadpoolWorker
56 61c8 ThreadpoolWorker
58 1fa4 ThreadpoolWorker
60 2788 ThreadpoolWorker
69 48f4 IOCompletion
70 5708 IOCompletion
71 3b58 ThreadpoolWorker
72 17a0 GC
73 2f00 Gate
74 35e8 IOCompletion
75 5730 IOCompletion

可以看到当前的 44 号线程就是触发 GC 的线程,接下来就明朗了,看看 44 号线程在做啥?切到 44 号线程,然后 !clrstack 即可。


0:044> !clrstack
OS Thread Id: 0x5bc4 (44)
Child SP IP Call Site
000000A2B0C3E4C8 00007ffd471ead3a [HelperMethodFrame: 000000a2b0c3e4c8]
000000A2B0C3E5E0 00007ffce8b4e506 System.Collections.Generic.List`1[[System.__Canon, System.Private.CoreLib]].System.Collections.Generic.IEnumerable.GetEnumerator() [/_/src/System.Private.CoreLib/shared/System/Collections/Generic/List.cs @ 585]
000000A2B0C3E630 00007ffce85e7a10 xxx.Program.DeletexxxExipredDate()
000000A2B0C3E780 00007ffd46bc1f0b System.Threading.ThreadHelper.ThreadStart_Context(System.Object) [/_/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 44]
000000A2B0C3E7B0 00007ffd46bb90b6 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/System.Private.CoreLib/shared/System/Threading/ExecutionContext.cs @ 172]
000000A2B0C3E830 00007ffd46ba535b System.Threading.ThreadHelper.ThreadStart() [/_/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 93]
000000A2B0C3EA48 00007ffd47236c93 [GCFrame: 000000a2b0c3ea48]
000000A2B0C3ECB0 00007ffd47236c93 [DebuggerU2MCatchHandlerFrame: 000000a2b0c3ecb0]

从输出信息看,问题出在了 DeletexxxExipredDate() 方法,接下来探究下这个方法的源码。


private static void DeletexxxExipredDate()
{
while (true)
{
foreach (string key in xxx.xxxSpeedLimit.Keys)
{
try
{
string[] array = xxx.xxxSpeedLimit[key].Split('$');
if (array.Length > 1)
{
DateTime dateTime = Convert.ToDateTime(array[1]);
if ((DateTime.Now - dateTime).TotalSeconds > 21600.0 && xxx.xxxSpeedLimit.ContainsKey(key))
{
xxx.xxxSpeedLimit.TryRemove(key, out var _);
}
}
}
catch (Exception ex)
{
LogHelper.WriteAppExceptionLog("删除数据出现异常:" + ex.Message, ex);
}
Thread.Sleep(20000);
}
}
}

如果有丰富踩坑经验的朋友,我相信一眼就能看出这代码中存在的问题,对,就是当 xxxSpeedLimit 字典为空的时候,就相当于一个 while(true) 死循环啦,对不对?

为了验证我的说法,可以用 !dso 找到 dict 的内存地址,然后用 !wconcurrentdict 即可。


0:044> !dso
OS Thread Id: 0x5bc4 (44)
RSP/REG Object Name
...
000000A2B0C3E708 000001ba8007f618 System.Collections.Concurrent.ConcurrentDictionary`2[[System.String, System.Private.CoreLib],[System.String, System.Private.CoreLib]]
000000A2B0C3E760 000001ba88501cd0 System.Collections.Generic.List`1+Enumerator[[System.String, System.Private.CoreLib]]
000000A2B0C3E768 000001ba80050ec0 System.Threading.ContextCallback
000000A2B0C3E7F8 000001ba80a1a818 System.Threading.Thread
000000A2B0C3EA28 000001ba80a1a898 System.Threading.ThreadStart
0:044> !wconcurrentdict 000001ba8007f618
Empty ConcurrentDictionary

可以看到,当前就是一个 空字典

三:总结

这次事故的主要原因:编码人员缺少了一定的编程经验,在写业务逻辑的时候缺少了对 空字典 的流程处理,导致了 while(true) 的尴尬,也有可能是将那个 Thread.Sleep(20000) 放错了位置

总的来说很感谢这位朋友提供的dump,让我真的眼见为实啦!

记一次 .NET 某智能交通后台服务 CPU爆高分析的更多相关文章

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

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

  2. 记一次 .NET 车联网云端服务 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序CPU经常飙满,没找到原因,希望帮忙看一下. 这些天连续接到几个cpu爆高的dump,都看烦了,希望后面再来几个其他方面的dump,从沟通上看, ...

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

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

  4. 记一次 .NET 某娱乐聊天流平台 CPU 爆高分析

    一:背景 1.讲故事 前段时间有位朋友加微信,说他的程序直接 CPU=100%,每次只能手工介入重启,让我帮忙看下到底怎么回事,哈哈,这种CPU打满的事故,程序员压力会非常大, 我让朋友在 CPU 高 ...

  5. 记一次 .NET 某电商交易平台Web站 CPU爆高分析

    一:背景 1. 讲故事 已经连续写了几篇关于内存暴涨的真实案例,有点麻木了,这篇换个口味,分享一个 CPU爆高 的案例,前段时间有位朋友在 wx 上找到我,说他的一个老项目经常收到 CPU > ...

  6. 记一次 .NET 某物管后台服务 卡死分析

    一:背景 1. 讲故事 这几个月经常被朋友问,为什么不更新这个系列了,哈哈,确实停了好久,主要还是打基础去了,分析 dump 的能力不在于会灵活使用 windbg,而是对底层知识有一个深厚的理解,比如 ...

  7. 记一次 .NET 差旅管理后台 CPU 爆高分析

    一:背景 1. 讲故事 前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术 ...

  8. 记一次 .NET游戏站程序的 CPU 爆高分析

    一:背景 1. 讲故事 上个月有个老朋友找到我,说他的站点晚高峰 CPU 会突然爆高,发了两份 dump 文件过来,如下图: 又是经典的 CPU 爆高问题,到目前为止,对这种我还是有一些经验可循的. ...

  9. 记一次 .NET 某医院HIS系统 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...

随机推荐

  1. 【九度OJ】题目1080:进制转换 解题报告

    [九度OJ]题目1080:进制转换 解题报告 标签(空格分隔): 九度OJ 原题地址:http://ac.jobdu.com/problem.php?pid=1080 题目描述: 将M进制的数X转换为 ...

  2. 【LeetCode】946. Validate Stack Sequences 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 模拟过程 日期 题目地址:https://leetc ...

  3. 【LeetCode】338. Counting Bits 解题报告(Python & Java & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目描述 Given a non negati ...

  4. 【LeetCode】36. Valid Sudoku 解题报告(Python)

    [LeetCode]36. Valid Sudoku 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址 ...

  5. VMware15 虚拟机分别设置连接笔记本的WLAN和以太网双网络

    VMware15 虚拟机分别设置连接笔记本的WLAN和以太网双网络 虚拟机:window 10 主机: window 10 VVmware有3种网络连接模式:桥接.NAT.主机模式,默认分别对应VMN ...

  6. 从JVM设计角度解读Java内存模型

    第十六章:Java内存模型 本文我们将重点放在Java内存模型(JMM)的一些高层设计问题,以及JMM的底层需求和所提供的保证,还有一些高层设计原则背后的原理. 例如安全发布,同步策略的规范以及一致性 ...

  7. Layui的本地存储方法-Layui.data的基本使用

    本地存储是对 localStorage 和 sessionStorage 的友好封装,可更方便地管理本地数据. localStorage 持久化存储:layui.data(table, setting ...

  8. Jedis 连接池的基本使用

    jedis直连 每次操作都会创建一个jedis对象,执行完毕后关闭连接后释放,对应的就是一次Tcp连接. jedis连接池 预先生成一批jedis连接对象放入连接池中,当需要对redis进行操作时从连 ...

  9. MyBatis 二级缓存实现详解及使用注意事项

    二级缓存介绍 在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存.开启二级缓存后,会使用CachingExe ...

  10. linux 之 DolphinScheduler 安装步骤

    下载安装包 直接进官网下载 https://dolphinscheduler.apache.org/zh-cn/download/download.html 参考官方文档 https://dolphi ...