一:背景

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. 2021.11.02 eleveni的水省选题的记录

    2021.11.02 eleveni的水省选题的记录 因为eleveni比较菜,所以eleveni决定从绿题开始水 --实际上菜菜的eleveni连绿题都不一定能水过/忍不住哭了 [P2217 HAO ...

  2. 关于fiddler抓包一键生成python脚本

    本人贡献一篇关于抓包转换成脚本的文章 步骤一 打开fiddler,抓到包之后,保存成txt文件 步骤二 脚本里str_filename改成保存的文件名 步骤三 执行脚本一键转换 附上脚本,感谢关注~ ...

  3. 『现学现忘』Git基础 — 18、Git对象的总结

    目录 1.Git操作最基本的流程 2.工作目录中文件的状态 3.Git效率说明 提示:前面三篇文章已经分别的对blob对象.tree对象.commit对象进行了详细的说明,这篇文章我们总结一下,Git ...

  4. [题解] [AGC022E] Median Replace

    题目大意 有个奇数长度的 \(01\) 串 \(s\) 其中有若干位置是 \(?\). 每次可将 \(3\) 个连续的字符替换成这三个数的中位数. 求有多少方案将 \(?\) 替换成 \(0/1\) ...

  5. 【干货】BIOS、UEFI、MBR、GPT、GRUB 到底是什么意思?

    公众号关注 「开源Linux」 回复「学习」,有我为您特别筛选的学习资料~ 01 前言 在学习 Linux 系统启动原理之前,我们先了解下与操作系统启动相关的几个概念. 02 与操作系统启动相关的几个 ...

  6. SpringBoot整合MybatisPlus基本的增删改查,保姆级教程

    概述 MybatisPlus是国产的第三方插件, 它封装了许多常用的CURDapi,免去了我们写mapper.xml的重复劳动,这里介绍了基本的整合SpringBoot和基础用法. 引入依赖 在项目中 ...

  7. 异步加载数据——turn.js

    var tostore = GetQueryString("tostore"); var photo_id = GetQueryString("photo_id" ...

  8. 1┃音视频直播系统之浏览器中通过WebRTC访问摄像头

    一.WebRTC的由来 对于前端开发小伙伴而言,如果用 JavaScript 做音视频处理 在以前是不可想象的,因为首先就要考虑浏览器的性能是否跟得上音视频的采集 但是 Google 作为国际顶尖科技 ...

  9. Linux下切换root用户提示Authentication failure错误的解决方法(亲测有效)

    第一种情况可能是root密码输入错误造成的,再仔细检查一遍是否输入错误 第二种是刚安装完,没有设置root用户密码导致的,我的就是最小化安装,就会出现这种小问题 解决办法:sudo passwd 然后 ...

  10. 为什么说 Gradle 是 Android 进阶绕不去的坎 —— Gradle 系列(1)

    请点赞,你的点赞对我意义重大,满足下我的虚荣心. Hi,我是小彭.本文已收录到 GitHub · Android-NoteBook 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,欢迎 ...