一:背景

1. 讲故事

前段时间有位朋友在微信上找到我,说他的 web 系统 cpu 运行一段时候后就爆高了,让我帮忙看一下是怎么回事,那就看吧,声明一下,我看 dump 是免费的,主要是锤炼自己技术,没有某软工程师高额的技术分析费。

闲话不多说,我们上 windbg 说话。

二:WinDbg 分析

1. CPU 真的爆高吗

是否真的爆高,我们得自己先验证下,使用 !tp 命令看一下即可。


0:065> !tp
CPU utilization: 81%
Worker Thread: Total: 32 Running: 7 Idle: 25 MaxLimit: 8191 MinLimit: 32
Work Request in Queue: 1
AsyncTimerCallbackCompletion TimerInfo@018eedc8
--------------------------------------
Number of Timers: 1
--------------------------------------
Completion Port Thread:Total: 4 Free: 4 MaxFree: 64 CurrentLimit: 4 MaxLimit: 1000 MinLimit: 32

从卦象看,确实存在 CPU 爆高的情况,根据过往经验,托管程序爆高大多是因为GC触发所致,但触发 GC 的原因千奇百怪,毕竟在 clr 层面 GC 触发的原因高达 14 种,代码如下:


static const char* const str_gc_reasons[] =
{
"alloc_soh",
"induced",
"lowmem",
"empty",
"alloc_loh",
"oos_soh",
"oos_loh",
"induced_noforce",
"gcstress",
"induced_lowmem",
"induced_compacting",
"lowmemory_host",
"pm_full_gc",
"lowmemory_host_blocking"
};

2. 真的是 GC 触发吗

验证当前程序是否为 GC 触发,方式有很多,可以用 !t 或者 !t -special,但这两种方式不是特别准,最准的就是根据GC模式直接到 CLR 里去搜全局变量 clr!SVR::gc_heap::gc_started 的值就可以了,参考如下:


0:038> dp clr!SVR::gc_heap::gc_started L1
712d3190 00000001

可以看到,此时的 gc_started=1,说明 GC 是触发状态,接下来可以从所有的线程栈中搜 garbage_collect 或者 gc1 什么的关键词即可。


0:038> k
# ChildEBP RetAddr
00 0318f934 70de8248 clr!SVR::gc_heap::relocate_survivor_helper+0x1ea
01 0318f944 70de83df clr!SVR::gc_heap::relocate_survivors_in_plug+0x24
02 0318f970 70de84ac clr!SVR::gc_heap::relocate_survivors_in_brick+0x70
03 0318f9a8 70de830b clr!SVR::gc_heap::relocate_survivors+0xe4
04 0318fa00 70de218a clr!SVR::gc_heap::relocate_phase+0xb9
05 0318fbb4 70de18bf clr!SVR::gc_heap::plan_phase+0x136e
06 0318fbec 70de1d49 clr!SVR::gc_heap::gc1+0x101
07 0318fc3c 70de1421 clr!SVR::gc_heap::garbage_collect+0x746
08 0318fc58 70ddacde clr!SVR::gc_heap::gc_thread_function+0x14a
09 0318fc6c 70ddac6f clr!SVR::gc_heap::gc_thread_stub+0x72
0a 0318fc80 770a6a14 clr!GCThreadStub+0x1f
0b 0318fc94 77e4a9ef kernel32!BaseThreadInitThunk+0x24
0c 0318fcdc 77e4a9ba ntdll!__RtlUserThreadStart+0x2f
0d 0318fcec 00000000 ntdll!_RtlUserThreadStart+0x1b

从卦象看,gc_thread_stub 表示当前是一个 GC 线程,它正在处于 relocate_phase 阶段,这表明当前是一个 压缩回收,GC回收流程图可以看下官方文档。


GarbageCollectGeneration()
{
SuspendEE();
garbage_collect();
RestartEE();
} garbage_collect()
{
generation_to_condemn();
gc1();
} gc1()
{
mark_phase();
plan_phase();
} plan_phase()
{
// actual plan phase work to decide to
// compact or not
if (compact)
{
relocate_phase();
compact_phase();
}
else
make_free_lists();
}

在这个阶段,托管堆也会是损坏状态,你可以用 !dumpheap -stat 验证下。


0:038> !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.
Object <exec cmd="!ListNearObj /d 03301000">03301000</exec> has an invalid method table.

3. 为什么会出现压缩回收

一般来说,GC 分清除和压缩回收,后者属于一种重量级操作,很伤GC,在临时段上还稍微好一些,接下来我们看下当前 GC 是在回收哪一代?可以到 CLR 里面去查一下判决代字段。clr!WKS::GCHeap::GcCondemnedGeneration


0:038> dp clr!SVR::GCHeap::GcCondemnedGeneration L1
712d79d8 00000002

糟了,结果是个 2,这个 2 表示 fullGC, 也就是全量回收,大多对应着 gc_reason=lowmem 的情况,也就是内存不足。

4. 真的内存不足吗

要想找到答案,我们用 !address -summary 看下当前的虚拟内存情况。


0:038> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unknown> 1835 dce6e000 ( 3.452 GB) 91.56% 86.29%
Image 842 f436000 ( 244.211 MB) 6.33% 5.96%
Free 312 eba5000 ( 235.645 MB) 5.75%
Stack 451 2d80000 ( 45.500 MB) 1.18% 1.11%
Heap 72 2342000 ( 35.258 MB) 0.91% 0.86%
TEB 150 96000 ( 600.000 kB) 0.02% 0.01%
Other 7 4e000 ( 312.000 kB) 0.01% 0.01%
PEB 1 1000 ( 4.000 kB) 0.00% 0.00% --- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE 2051 dd635000 ( 3.459 GB) 91.76% 86.48%
MEM_IMAGE 1267 11ad1000 ( 282.816 MB) 7.33% 6.90%
MEM_MAPPED 40 2345000 ( 35.270 MB) 0.91% 0.86% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT 2604 cbe7d000 ( 3.186 GB) 84.51% 79.65%
MEM_RESERVE 754 255ce000 ( 597.805 MB) 15.49% 14.60%
MEM_FREE 312 eba5000 ( 235.645 MB) 5.75% --- Largest Region by Usage ----------- Base Address -------- Region Size ----------
<unknown> 3300000 20087000 ( 512.527 MB)
Image 6f819000 f5f000 ( 15.371 MB)
Free fea50000 1590000 ( 21.562 MB)
Stack 3110000 7a000 ( 488.000 kB)
Heap 3bc80000 621000 ( 6.129 MB)
TEB fe6e5000 1000 ( 4.000 kB)
Other fea10000 33000 ( 204.000 kB)
PEB fea49000 1000 ( 4.000 kB)

从卦象看,当前的 MEM_COMMIT=3.186G, 最大的Free块 Free=15.371MB,再根据之前展示的内存地址,我们发现这个程序是 32bit ,跑了 64bit 机器上,这种情况下程序最多可占用 4G 内存空间,虽然 MEM_RESERVE= 597.805 MB, 但这种 RESERVE 是零散的,本质上来说此时的程序处于虚拟地址紧张,由于 虚拟地址 紧张,导致 GC 在不断的做 全量内存 回收。

三:总结

根据上面的分析, GC 触发的原因主要还是 32bit 程序的 4G 内存限制所致, 导致 GC 在不停的做全量回收,这种场景真的让 GC 很尴尬,优先解决办法就是将程序改成 64bit,后面再看看如何优化程序内存,毕竟现在托管堆处于损坏状态,也不好分析啦。

记一次 .NET 差旅管理后台 CPU 爆高分析的更多相关文章

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

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

  2. 记一次 .NET 某电子病历 CPU 爆高分析

    一:背景 1.讲故事 前段时间有位朋友微信找到我,说他的程序出现了 CPU 爆高,帮忙看下程序到底出了什么情况?图就不上了,我们直接进入主题. 二:WinDbg 分析 1. CPU 真的爆高吗? 要确 ...

  3. 记一次 .NET 某资讯论坛 CPU爆高分析

    大概有11天没发文了,真的不是因为懒,本想前几天抽空写,不知道为啥最近求助的朋友比较多,一天都能拿到2-3个求助dump,晚上回来就是一顿分析,有点意思的是大多朋友自己都分析了几遍或者公司多年的牛皮藓 ...

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

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

  5. 记一次 .NET 某机械臂智能机器人控制系统MRS CPU爆高分析

    一:背景 1. 讲故事 这是6月中旬一位朋友加wx求助dump的故事,他的程序 cpu爆高UI卡死,问如何解决,截图如下: 在拿到这个dump后,我发现这是一个关于机械臂的MRS程序,哈哈,在机械臂这 ...

  6. 记一次 .NET 某智能交通后台服务 CPU爆高分析

    一:背景 1. 讲故事 前天有位朋友加微信求助他的程序出现了CPU爆高的问题,开局就是一个红包,把我吓懵了! 由于是南方小年,我在老家张罗处理起来不方便,没有第一时间帮他处理,朋友在第二天上午已经找出 ...

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

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

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

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

  9. 记一次 .NET 某旅行社Web站 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序内存经常飙升,cpu 偶尔飙升,没找到原因,希望帮忙看一下. 可惜发过来的 dump 只有区区2G,能在这里面找到内存泄漏那真有两把刷子..., ...

随机推荐

  1. android软件简约记账app开发day10-主页面模块--头信息的展示,和之后功能完善的目标。

    android软件简约记账app开发day10-主页面模块--头信息的展示,和之后功能完善的目标. 今天来写主界面头信息的展示,也就是将第一天的写的layout中的item_main_top展示到主界 ...

  2. Jx.Cms开发笔记(五)-文章编辑页面标签设计

    标签页的样子 设计思路 与其他输入框一样,存在一个Label标签,由于这里不像其他输入框一样可以直接使用Row标签,所以这里需要额外增加. 使用Tag组件显示所有的标签,我们在Blazor 组件库 B ...

  3. 【阿里巴巴Java开发手册1.7.0(嵩山版)】编程规约&MySQL 数据库规约

    阿里巴巴Java开发手册1.7.0(嵩山版) 一.编程规约 (一)命名风格 所有命名不得以下划线和$开始和结束. 所有命名不得以拼音或拼音英文混合. 类名使用UpperCamelCase风格. 方法名 ...

  4. 攻防世界-MISC:掀桌子

    这是攻防世界新手练习区的第八题,题目如下: 就给了一串16进制的字符串.哎,又是不懂,看了一下官方WP,说是将每两位16进制数转换为10进制,再减去128再转换为ASCII码.直接上脚本 str1 = ...

  5. SD卡之二:SD总线访问模式

    SD 卡是以命令.回应.数据流进行通讯. 1.命令:命令的长度是48位,命令以'0'开始,第2位为'1'表示主机发往SD卡的命令,最后以CRC和结束位'1'结尾. 2.回应:回应的长度是48位或者13 ...

  6. 记录一下l联想Y7000安装双系统(win10+ubuntu16.04)

    单位新配的联想拯救者Y7000,感觉很不错哈,先上一张图. 说实在的,装这个有些小坑,我最开始是直接在原装win10上去装双系统的,结果死活装不上,还把原装win10给折腾没了,哈哈,好逗,以前装双系 ...

  7. 解决学校在线评测系统不支持C++11的问题

    如图,我们老师搞的这个评测系统它不支持C++11.但分析错误信息可知GCC本身版本是支持C++11的,只不过没开开.平时的时候我们可以对"g++"后使用"-std=c++ ...

  8. 一个关于 useState 的误解

    一个关于 useState 的误解 本文写于 2020 年 11 月 17 日 前两天有人问了我一个问题,他有一段这样的代码: function App() { const [n, setN] = u ...

  9. 华为OPS,自定义命令,动态执行命令

     OPS     开放可编程系统OPS(Open Programmability System)是指设备通过提供统一的应用程序接口API(Application Programming Interfa ...

  10. 一、全新安装搭建redis主从集群

    前言· 这里分为三篇文章来写我是如何重新搭建redis主从集群和哨兵集群的及原本服务器上有单redis如何通过升级脚本来实现redis集群.(redis结构:主-从(备)-从(备)) 至于为什么要搭建 ...