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

一:背景

1. 讲故事

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

话不多说,上 windbg 说话。

二:WinDbg 分析

1. CPU 真的爆高吗

既然说 CPU 爆高,那就用 !tp 验证下。

0:000> !tpMethod table is shared (not implemented): System.Threading.ThreadPool
CPU utilization: 81 Unknown format characterUnknown format control characterWorker Thread: Total: 203 Running: 183 Idle: 0 MaxLimit: 300 MinLimit: 150
Work Request in Queue: 0
--------------------------------------
Number of Timers: 40
--------------------------------------
Completion Port Thread:Total: 21 Free: 21 MaxFree: 80 CurrentLimit: 21 MaxLimit: 300 MinLimit: 150

从卦中看确实 CPU=81%,不过输出信息很奇怪,方法表都出错了,猜的不错应该是触发 GC 把 托管堆给关闭了,源码如下:

GCScan::GcRuntimeStructuresValid (FALSE);plan_phase (n);GCScan::GcRuntimeStructuresValid (TRUE);

也可以用 !dumpheap -stat 来验证。

0:000> !dumpheap -stat
The garbage collector data structures are not in a valid state for traversal.
It is either in the "plan phase," where objects are being moved around, or
we are at the initialization or shutdown of the gc heap. Commands related to 
displaying, finding or traversing objects as well as gc heap segments may not 
work properly. !dumpheap and !verifyheap may incorrectly complain of heap 
consistency errors.
Could not request method table data for object 000001E49376D520 (MethodTable: FFFFFFFFFFE026C0).

2. 为什么会触发 GC

此时我们已知道是 GC 触发,接下来可以通过 !t + !clrstack 找到那个触发 GC 的线程,通过线程栈看看正在干嘛 ?

0:000> !t 
ThreadCount:      382
UnstartedThread:  0
BackgroundThread: 340
PendingThread:    0
DeadThread:       0
Hosted Runtime:   noLock  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception0    1     1ba4 000001E45C018C90    2a020 Preemptive  0000000000000000:0000000000000000 000001e45c368cb0 1     MTA 297  286     2144 000001E478521200  1029220 Cooperative 0000000000000000:0000000000000000 000001e45c368cb0 0     MTA (GC) (Threadpool Worker) 0:297> !clrstack 
OS Thread Id: 0x2144 (297)Child SP               IP Call Site0e 0000002f`2927ade0 00007ffa`afda2096     coreclr!WKS::gc_heap::garbage_collect+0x2a1 [e:\a\_work\191\s\src\gc\gc.cpp @ 16967] 
0f 0000002f`2927aee0 00007ffa`afdbe746     coreclr!WKS::GCHeap::GarbageCollectGeneration+0x156 [e:\a\_work\191\s\src\gc\gc.cpp @ 35107] 
10 (Inline Function) --------`--------     coreclr!WKS::gc_heap::try_allocate_more_space+0x1f5 [e:\a\_work\191\s\src\gc\gc.cpp @ 13197] 
11 0000002f`2927af30 00007ffa`afd80c9f     coreclr!WKS::gc_heap::allocate_more_space+0x216 [e:\a\_work\191\s\src\gc\gc.cpp @ 13490] 
12 (Inline Function) --------`--------     coreclr!WKS::gc_heap::allocate+0x37e [e:\a\_work\191\s\src\gc\gc.cpp @ 13521] 
13 (Inline Function) --------`--------     coreclr!WKS::GCHeap::Alloc+0x3e5 [e:\a\_work\191\s\src\gc\gc.cpp @ 34419] 
14 (Inline Function) --------`--------     coreclr!Alloc+0x4be [e:\a\_work\191\s\src\vm\gchelpers.cpp @ 241] 
15 (Inline Function) --------`--------     coreclr!AllocateObject+0x512 [e:\a\_work\191\s\src\vm\gchelpers.cpp @ 1156] 
16 0000002f`2927af90 00007ffa`51c05122     coreclr!JIT_New+0x5ff [e:\a\_work\191\s\src\vm\jithelpers.cpp @ 2810] 
...
0000002F2927B228 00007ffaafd63aff [HelperMethodFrame: 0000002f2927b228] 
0000002F2927B340 00007ffa51c05122 Jint.Native.Object.ObjectInstance..ctor(Jint.Engine)
0000002F2927B380 00007ffa51c058aa Jint.Native.Array.ArrayConstructor.CreateArrayConstructor(Jint.Engine)
0000002F2927B3D0 00007ffa51c0407c Jint.Engine..ctor(System.Action`1<Jint.Options>)
...

由于信息比较敏感,我就不过多的输出了,不过可以看出 GC 的引发是由于 Jint 组件,查了下资料是 JavaScript.NET 用来交互的,为了进一步验证,观察下此时 GC 触发的代以及什么原因。

0:297> dx -r1 (*((coreclr!WKS::gc_mechanisms *)0x7ffab021df90))
(*((coreclr!WKS::gc_mechanisms *)0x7ffab021df90))                 [Type: WKS::gc_mechanisms][+0x000] gc_index         : 0x984ab [Type: unsigned __int64][+0x008] condemned_generation : 0 [Type: int][+0x00c] promotion        : 1 [Type: int][+0x010] compaction       : 1 [Type: int][+0x014] loh_compaction   : 0 [Type: int][+0x018] heap_expansion   : 0 [Type: int][+0x01c] concurrent       : 0x0 [Type: unsigned int][+0x020] demotion         : 1 [Type: int][+0x024] card_bundles     : 1 [Type: int][+0x028] gen0_reduction_count : 0 [Type: int][+0x02c] should_lock_elevation : 0 [Type: int][+0x030] elevation_locked_count : 0 [Type: int][+0x034] elevation_reduced : 0 [Type: int][+0x038] minimal_gc       : 0 [Type: int][+0x03c] reason           : reason_alloc_soh (0) [Type: gc_reason][+0x040] pause_mode       : pause_interactive (1) [Type: WKS::gc_pause_mode][+0x044] found_finalizers : 1 [Type: int][+0x048] background_p     : 0 [Type: int][+0x04c] b_state          : bgc_not_in_process (0) [Type: bgc_state][+0x050] allocations_allowed : 1 [Type: int][+0x054] stress_induced   : 0 [Type: int][+0x058] entry_memory_load : 0x0 [Type: unsigned int][+0x05c] exit_memory_load : 0x0 [Type: unsigned int]

从卦中看,当前触发的是 0 代GC,触发原因是 0代 的阈值满了,这是一个很正常的 GC 操作,理应不会造成 CPU 爆高,除非是那些伤害性比较大的 FULLGC,由于没有更多的 dump 可以参考,到这里就没法更进一步确认了。

3. 还有其他线索吗

虽然 .NET 程序大多 CPU 爆高是由于 GC 的频繁触发所致,但也有其他情况,比如 CPU 密集型操作往往也会,就像我之前解读 B站的LUA死循环导致的CPU爆高场景下如何通过 火焰图 去寻找热点函数。

那这个 dump 会不会也存在这种情况呢?不管有没有,在一个 dump 的情况下也只能 死马当作活马医 了,可以用 !runaway 查查当前线程运行时间。

0:297> !runawayUser Mode TimeThread       Time269:2354     0 days 0:07:04.171274:15d4     0 days 0:06:16.453280:1c98     0 days 0:05:32.406284:438      0 days 0:04:37.703283:183c     0 days 0:04:29.531282:122c     0 days 0:04:24.703288:2060     0 days 0:03:59.953286:28d0     0 days 0:03:56.640289:2a84     0 days 0:03:50.859290:1224     0 days 0:03:44.640291:2e4c     0 days 0:03:29.937292:f0c      0 days 0:03:28.656293:2454     0 days 0:03:26.640275:2810     0 days 0:03:23.828294:2f34     0 days 0:03:22.312295:24ec     0 days 0:03:17.625297:2144     0 days 0:03:16.609298:2c34     0 days 0:03:14.609299:2480     0 days 0:03:11.218...

线程还是蛮多的,采样几个看一下,发现有很多函数与 序列化 有关。

0:269> !clrstack 
OS Thread Id: 0x2354 (269)Child SP               IP Call Site
0000002F080FD658 00007ffacb236124 [HelperMethodFrame: 0000002f080fd658] 
0000002F080FD770 00007ffab11d806b System.Runtime.Serialization.Formatters.Binary.SizedArray..ctor() [E:\A\_work\322\s\corefx\src\System.Runtime.Serialization.Formatters\src\System\Runtime\Serialization\Formatters\Binary\BinaryUtilClasses.cs @ 203]
0000002F080FD7A0 00007ffab11d6964 System.Runtime.Serialization.Formatters.Binary.BinaryParser.get_ObjectMapIdTable() [E:\A\_work\322\s\corefx\src\System.Runtime.Serialization.Formatters\src\System\Runtime\Serialization\Formatters\Binary\BinaryParser.cs @ 57]
0000002F080FD7E0 00007ffa515132c1 System.Runtime.Serialization.Formatters.Binary.BinaryParser.ReadObjectWithMapTyped(System.Runtime.Serialization.Formatters.Binary.BinaryObjectWithMapTyped) [E:\A\_work\322\s\corefx\src\System.Runtime.Serialization.Formatters\src\System\Runtime\Serialization\Formatters\Binary\BinaryParser.cs @ 532]
0000002F080FD8B0 00007ffab11d74ed System.Runtime.Serialization.Formatters.Binary.BinaryParser.ReadObjectWithMapTyped(System.Runtime.Serialization.Formatters.Binary.BinaryHeaderEnum) [E:\A\_work\322\s\corefx\src\System.Runtime.Serialization.Formatters\src\System\Runtime\Serialization\Formatters\Binary\BinaryParser.cs @ 504]0:280> !clrstack 
OS Thread Id: 0x1c98 (280)Child SP               IP Call Site
0000002F185FCE38 00007ffacb236124 [HelperMethodFrame: 0000002f185fce38] 
0000002F185FCF30 00007ffaaf59bb61 System.String.Ctor(Char[], Int32, Int32) [E:\A\_work\191\s\src\mscorlib\shared\System\String.cs @ 79]
0000002F185FCF90 00007ffa5033f984 Newtonsoft.Json.JsonTextReader.ParseReadString(Char, Newtonsoft.Json.ReadType)
0000002F185FD040 00007ffa5099cd0b Newtonsoft.Json.JsonTextReader.ReadStringValue(Newtonsoft.Json.ReadType)
0000002F185FD0B0 00007ffa5099cb0e Newtonsoft.Json.JsonTextReader.ReadAsString()
0000002F185FD0E0 00007ffa514c68fc Newtonsoft.Json.JsonReader.ReadForType(Newtonsoft.Json.Serialization.JsonContract, Boolean)0:284> !clrstack 
OS Thread Id: 0x438 (284)Child SP               IP Call Site
0000002F1ED7C9C8 00007ffacb236124 [RedirectedThreadFrame: 0000002f1ed7c9c8] 
0000002F1ED7CA48 00007ffaaf5a6863 System.Buffer.Memmove(Byte*, Byte*, UInt64) [E:\A\_work\191\s\src\mscorlib\src\System\Buffer.cs @ 211]
0000002F1ED7CA50 00007ffaaf59bbb2 System.String.Ctor(Char[], Int32, Int32) [E:\A\_work\191\s\src\mscorlib\shared\System\String.cs @ 83]
0000002F1ED7CAB0 00007ffa5033f984 Newtonsoft.Json.JsonTextReader.ParseReadString(Char, Newtonsoft.Json.ReadType)
0000002F1ED7CB60 00007ffa5099cd0b Newtonsoft.Json.JsonTextReader.ReadStringValue(Newtonsoft.Json.ReadType)
0000002F1ED7CBD0 00007ffa5099cb0e Newtonsoft.Json.JsonTextReader.ReadAsString()

有了线索之后,接下来用 ~*e !clrstack 把所有的线程栈调出来,发现很多的 JsonConvert ,并且还有 5 个线程在做 DeepClone,截图如下:

0a225206c0d3218e79c221f87df5fb6d.png

接下来把 DeepClone 函数导出来看看,发现是用 BinaryFormatter 来实现对象的深复制。

public static T DeepClone<T>(this T obj) where T : class
{BinaryFormatter binaryFormatter = new BinaryFormatter();using MemoryStream memoryStream = new MemoryStream();binaryFormatter.Serialize(memoryStream, obj);memoryStream.Seek(0L, SeekOrigin.Begin);return (T)binaryFormatter.Deserialize(memoryStream);
}

把发现的这些线索反馈给朋友后,确实也验证了是 序列化 造成的。

7765f767e4868be071a7bc6f5cc6f538.png

三:总结

分析完毕,这个 dump 给我们的教训是:

  1. 对象的深复制慎用 BinaryFormatter 这种流式操作,尤其是在大对象的情况下,它是一种 CPU 密集性的,建议采用 AutoMapper 这类 带 ILEmit, ExpressionTree 还带编译缓存的开源工具包。

  2. 高级调试是一场破案之旅,你第一眼看到的往往是程序故意让你看到的,需要不断的积累破案经验练就一双慧眼。

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

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

相关文章

采用Atlas+Keepalived实现MySQL读写分离、读负载均衡【转载】

文章 原始出处 &#xff1a;http://sofar.blog.51cto.com/353572/1601552 一、基础介绍 1、背景描述 目前我们的高可用DB的代理层采用的是360开源的Atlas&#xff0c;从上线以来&#xff0c;已稳定运行2个多月。无论是从性能上&#xff0c;还是稳定性上&#xff0c;相比其他开…

vscode搭建go开发环境

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装goLang二.配置环境变量三、vscode安装插件四.安装golang依赖五.新建go文件前言 能用golang就用golang..这配置很麻烦 提示&#xff1a;以下是本篇文章正…

【GlobalMapper精品教程】024:批量高效实现多种数据格式互转的方法

globalmapper批量高效实现多种数据格式互转的方法。 文章目录一、批量格式转换二、格式转换形式举例一、批量格式转换 选择原文件类型&#xff1a; 选择文件类型&#xff1a; 在源文件列表中添加需要转换的文件或者文件夹&#xff0c;指定目标文件目录&#xff0c;文件名称和投…

基于 WeihanLi.Npoi 实现excel导入时纯汉字的日期转换

基于 WeihanLi.Npoi 实现excel导入时纯汉字的日期转换Intro前段时间有位小伙伴在 Github 上提了一个 “不能识别纯汉字的日期格式” issue二〇二二年一月一日 格式的日期单元格识别不出来会变成&#xff0c;0001/1/1 0:00:00 如何让它能够识别出来呢&#xff0c;基于 InputForm…

十个模型,总结产品经理沟通方法论

编辑导语&#xff1a;毫不夸张地说沟通占据了产品经理日常工作内容的40%&#xff0c;高效沟通往往能让事情事半功倍。本文作者结合沟通方法与具体沟通情景讲解了如何高效沟通&#xff0c;一起来看看吧&#xff01; 目录 一、为什么要学会沟通 二、沟通模型 1. PREP原则&…

【Globalmapper中文入门到精通系列实验图文教程】(附配套实验数据持续更新)

【Globalmapper中文版入门到精通系列实验图文教程】&#xff08;附配套实验数据持续更新&#xff09; 文章目录一、专栏简介二、文章目录三、数据目录四、传送门一、专栏简介 本专栏为GlobalMapper中文入门实战精品教程&#xff0c;内容主要涉及&#xff1a;Globalmapper23软件…

【GlobalMapper精品教程】025:影像数据集的建立与巧妙使用

GlobalMapper影像数据集类似于金字塔,作用是提高大量影像的加载与显示速度,还可批量进行一系列设置。本文的配套数据为data025.rar。 文章目录 1. 建立影像数据集2. 影像数据集的使用1. 建立影像数据集 (1)点击【文件】→【创建新地图目录】。 (2)选择影像数据集存放路径…

使用xUnit为.net core程序进行单元测试(3)

第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html 请使用这个项目作为练习的开始: https://pan.baidu.com/s/1ggcGkGb 测试的分组 打开Game.Tests里面的BossEnemyShould.cs, 为HaveCorrectPower方法添加一个Trait属性…

CDN的强大功能

2019独角兽企业重金招聘Python工程师标准>>> CDN&#xff0c;内容分发网络&#xff0c;除了用作网站加速外&#xff0c;还能够更好的保护网站不被攻击。防护网站不被攻击的功能成就了CDN运行中的主要责任。CDN 防护原理是其主要在于在相关节点中成功的建立动态加速机…

IDEA创建SpringBoot项目无法连接https://start.spring.io(已解决)

错误&#xff1a; 方法&#xff1a; 将&#xff1a;https://start.spring.io 更换为 ​https://start.aliyun.com

2005年AMC8数学竞赛中英文真题典型考题、考点分析和答案解析

今天距离2024年的AMC8美国数学竞赛举办已不足一个月了&#xff0c;赶紧利用周末的时间刷刷真题&#xff0c;查漏补缺吧&#xff01;如果您有任何关于AMC8比赛的任何问题都可以问我&#xff0c;关于题目的解析也可以交流。 今天我们来看看2005年AMC8竞赛的五道典型考题。欢迎您查…

WPF效果第一百九十三篇之登录实现

前面一直在玩耍ListBox(最爱),大周末的就适合在家吹着风扇撸着代码;今天来分享一个很简单实用的登录,来看看最终实现的效果:1、关于软件启动后焦点实现:<Style TargetType"Border"><Style.Triggers><DataTrigger Binding"{Binding IsEmptyAccoun…

IDEA中安装并使用JRebel热部署插件

文章目录 作者简介引言导航热门专栏推荐概述安装JRebel注册JRebel配置JRebel最后小结导航热门专栏推荐作者简介 作者名&#xff1a;编程界明世隐 简介&#xff1a;CSDN博客专家&#xff0c;从事软件开发多年&#xff0c;精通Java、JavaScript&#xff0c;博主也是从零开始一步步…

UWP: 实现 UWP 应用自启动

原文:UWP: 实现 UWP 应用自启动在上一篇文章中&#xff0c;我们实现了使用命令行来启动 UWP 应用&#xff0c;在这一篇文章中&#xff0c;我们会实现 UWP 应用自启用的实现&#xff0c;也即开机后或用户登陆后&#xff0c;应用自己启动。这些特性原来都是 Win32 程序所具备的&a…

选择 GCD 还是 NSTimer ?

我们常常会延迟某件任务的执行&#xff0c;或者让某件任务周期性的执行。然后也会在某些时候需要取消掉之前延迟执行的任务。 延迟操作的方案一般有三种&#xff1a; 1.NSObject的方法&#xff1a; 2.使用NSTimer的方法&#xff1a; 3.使用GCD的方法&#xff1a; 一般情况下&am…

Web框架 性能评测 -- C# 的性能 和 Rust、C++并驾齐驱

自从2021年2月第20轮公布的测试以后&#xff0c;一年半后 的2022年7月19日 发布了 TechEmpower 21轮测试报告&#xff1a;Round 21 results - TechEmpower Framework Benchmarks。Techempower benchmark是包含范围最广泛的web框架性能测试&#xff0c;覆盖了比较典型的使用场景…

【GlobalMapper精品教程】027:路径剖面和和视线工具的使用

文章目录 一、路径剖面简介二、创建剖面图1. 加载DEM2. 创建剖面图3. 计算填挖方3. 保存剖面图一、路径剖面简介 路径剖面视线工具允许您使用加载的高程数据集沿用户指定的路径获取垂直剖面。 要定义生成3D路径剖面所遵循的路径,只需单击鼠标左键选择路径的点,然后石键单击…

QT中VideoProbe的简介和实现

一、遇到问题在Android机上使用QT进行图像处理程序设计的时候&#xff0c;遇到的一个比较明显的问题就是图片采集的问题----摄像头获得是实时的视频&#xff0c;如果我们想从中动态地截获图片&#xff0c;并且转换成Mat的格式&#xff0c;那么仅仅是静态的imagecapturee就无法完…

WinForm(二):WinFrom中Main函数的入参和出参

基本上有独立进程的应用&#xff0c;都是以Main函数作为入口&#xff0c;开始运行的。在C#中&#xff0c;Main函数可以无参无返回值&#xff0c;当然也可以是有string[]参数和int返返回值的。WinFrom也满足这个规则。那么Main作为一个进程的开始函数&#xff0c;那么是谁传这些…