一:背景

1. 讲故事

前些天有位朋友在微信上找到我,说他们的客户端程序卡死了,让我帮忙看下是什么原因导致的?dump也拿到了手,既然有了dump就开始正式分析吧。

二:WinDbg 分析

1. 什么导致的卡死

客户端的程序卡死比较好找原因,入手点就是主线程,看下它此时正在做什么,可以用 k 命令。


  1. 0:000> k
  2. # ChildEBP RetAddr
  3. 00 003cdf7c 74c115ce ntdll!NtWaitForSingleObject+0x15
  4. 01 003cdf7c 756e1194 KERNELBASE!WaitForSingleObjectEx+0x98
  5. 02 003cdf94 6f573bea kernel32!WaitForSingleObjectExImplementation+0x75
  6. 03 003cdfc4 6f573c31 clr!CLREventWaitHelper2+0x33
  7. 04 003ce014 6f573bb6 clr!CLREventWaitHelper+0x2a
  8. 05 003ce04c 6f57c8be clr!CLREventBase::WaitEx+0x152
  9. 06 003ce060 6f5764a9 clr!WKS::GCHeap::WaitUntilGCComplete+0x34
  10. 07 003ce0b0 6f583cf4 clr!Thread::RareDisablePreemptiveGC+0x231
  11. 08 003ce134 6a87a767 clr!JIT_RareDisableHelper+0x24
  12. 09 003ce16c 6a87472b System_Drawing_ni+0x4a767
  13. 0a 003ce17c 0846b372 System_Drawing_ni!System.Drawing.Graphics.Clear+0x1b
  14. ...

从卦中信息看,代码正在托管层做Graphics,突然程序触发了GC,因为STW的原因,clr需要使用SuspendRuntime把主线程导入到 WaitUntilGCComplete 进行等待,有了这些信息之后,接下来就是寻找为什么会触发GC。

2. 为什么会触发 GC

要找到GC触发原因,首先要找哪一个线程触发了GC,这个可以用 !t 看下托管线程列表中的 GC 字样,输出如下:


  1. 0:000> !t
  2. ThreadCount: 48
  3. UnstartedThread: 0
  4. BackgroundThread: 35
  5. PendingThread: 0
  6. DeadThread: 0
  7. Hosted Runtime: no
  8. Lock
  9. ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
  10. 48 42 ee8 0ee39f60 1029220 Cooperative 00000000:00000000 0076c700 3 MTA (GC) (Threadpool Worker)
  11. ...
  12. 0:048> k 10
  13. # ChildEBP RetAddr
  14. 00 4775c9e8 6f57d24e clr!WKS::gc_heap::mark_object_simple1+0x8a
  15. 01 4775ca14 6f57bf72 clr!WKS::gc_heap::mark_object_simple+0x22b
  16. 02 4775ca34 6f5774b2 clr!WKS::GCHeap::Promote+0xaa
  17. 03 4775ca4c 6f57809c clr!GcEnumObject+0x37
  18. 04 4775cdbc 6f5777cb clr!EECodeManager::EnumGcRefs+0x854
  19. 05 4775ce10 6f5723b9 clr!GcStackCrawlCallBack+0x167
  20. 06 4775d0dc 6f5724bf clr!Thread::StackWalkFramesEx+0x92
  21. 07 4775d410 6f57743b clr!Thread::StackWalkFrames+0x9d
  22. 08 4775d448 6f57ba0e clr!GCToEEInterface::GcScanRoots+0x108
  23. 09 4775d4a8 6f5792db clr!WKS::gc_heap::mark_phase+0x18a
  24. 0a 4775d4d0 6f57966f clr!WKS::gc_heap::gc1+0xda
  25. 0b 4775d508 6f57978c clr!WKS::gc_heap::garbage_collect+0x447
  26. 0c 4775d530 6f70b767 clr!WKS::GCHeap::GarbageCollectGeneration+0x1f6
  27. 0d 4775d590 6f70b7a3 clr!WKS::gc_heap::trigger_ephemeral_gc+0x1e
  28. 0e 4775d590 6f575f6f clr!WKS::gc_heap::allocate_small+0x270
  29. 0f 4775d5bc 6f575fca clr!WKS::gc_heap::try_allocate_more_space+0x17c
  30. ...

从卦中可以看到当前 48 号线程触发了GC,并且是处于三阶段中的标记阶段,接下来需要观察下到底触发的是哪一代GC,可以用 dt 观察下 setting 全局变量即可。


  1. 0:048> x clr!*settings*
  2. ...
  3. 6fbd4bd8 clr!WKS::gc_heap::settings = <no type information>
  4. 6fbd7538 clr!SVR::gc_heap::settings = <no type information>
  5. ...
  6. 0:048> dp clr!WKS::gc_heap::settings
  7. 6fbd4bd8 00002ce4 00000002 00000001 00000001
  8. 6fbd4be8 00000000 00000000 00000000 00000000
  9. 6fbd4bf8 00000001 00000000 00000000 00000000
  10. 6fbd4c08 00000000 00000000 00000005 00000001
  11. 6fbd4c18 00000000 00000000 00000000 00000001
  12. ...

从卦中的 +0x4 偏移可以看到当前触发的是 FullGC,从 +0x38 可以看到GC的触发原因是 reason_oos_soh = 5,有经验的朋友看到这里应该就知道是什么原因了。

3. 为什么会触发FullGC

相信大家都知道FullGC 有一个 STW 的概念,既然有STW自然就会让程序卡死,回过头来说一下经验在哪里,对,就是这个指针的长度,很显然这个程序是 32bit 的,所以很大概率程序是 32bit 部署,会受到 2G 虚拟地址的限制,因为可用内存不足导致高频的触发 FullGC,可以用 !address -summary 去验证一下。


  1. 0:048> !address -summary
  2. --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
  3. <unknown> 955 6029f000 ( 1.503 GB) 81.21% 75.13%
  4. Image 1251 10105000 ( 257.020 MB) 13.57% 12.55%
  5. Free 326 995d000 ( 153.363 MB) 7.49%
  6. Stack 165 34c0000 ( 52.750 MB) 2.78% 2.58%
  7. Heap 137 2db0000 ( 45.688 MB) 2.41% 2.23%
  8. Other 12 47000 ( 284.000 kB) 0.01% 0.01%
  9. TEB 55 37000 ( 220.000 kB) 0.01% 0.01%
  10. PEB 1 1000 ( 4.000 kB) 0.00% 0.00%
  11. ...
  12. --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
  13. MEM_COMMIT 2152 66fc7000 ( 1.609 GB) 86.97% 80.46%
  14. MEM_RESERVE 424 f6cc000 ( 246.797 MB) 13.03% 12.05%
  15. MEM_FREE 326 995d000 ( 153.363 MB) 7.49%
  16. ...

从卦中可以看到,当前程序吃了1.6G的虚拟地址,占全量的 80% ,这样情况按理说程序会抛 OutOfMemoryException 异常,在 !t 中也得到了验证。


  1. 0:048> !t
  2. ThreadCount: 48
  3. UnstartedThread: 0
  4. BackgroundThread: 35
  5. PendingThread: 0
  6. DeadThread: 0
  7. Hosted Runtime: no
  8. Lock
  9. ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
  10. ...
  11. 33 39 10c8 0ee3dec0 1029220 Preemptive 00000000:00000000 0076c700 0 MTA (Threadpool Worker) System.OutOfMemoryException 32614444 (nested exceptions)
  12. ...
  13. 46 44 89c 0ee3c458 1029220 Preemptive 00000000:00000000 0076c700 1 MTA (Threadpool Worker) System.OutOfMemoryException 32605d34 (nested exceptions)
  14. ...

接下来的问题是谁吃掉了 1.6G 的内存,总有地方会吃,可以使用 !eeheap -gc 观察下托管堆。


  1. 0:048> !eeheap -gc
  2. Number of GC Heaps: 1
  3. generation 0 starts at 0x32632d50
  4. generation 1 starts at 0x3262534c
  5. generation 2 starts at 0x03291000
  6. ephemeral segment allocation context: (0x3265ffb0, 0x3265ffbc)
  7. segment begin allocated size
  8. 03290000 03291000 0428fee4 0xffeee4(16772836)
  9. 06c60000 06c61000 07c5ffc4 0xffefc4(16773060)
  10. ...
  11. 7d210000 7d211000 7e20ffac 0xffefac(16773036)
  12. 31660000 31661000 3265ffb0 0xffefb0(16773040)
  13. Large object heap starts at 0x04291000
  14. segment begin allocated size
  15. 04290000 04291000 0450fa78 0x27ea78(2615928)
  16. 53390000 53391000 54391020 0x1000020(16777248)
  17. Total Size: Size: 0x4622fd30 (1176698160) bytes.
  18. ------------------------------
  19. GC Heap Size: Size: 0x4622fd30 (1176698160) bytes.

从卦中看应该就是托管堆吃掉了,接下来就是看下托管堆中哪一类对象最多,最终找到了一个大集合,命令输出如下:


  1. 0:048> !gcroot 4c0507c0
  2. Thread 89c:
  3. 471bd450 07f76405 IBatisNet.DataMapper.MappedStatements.MappedStatement.RunQueryForList[[System.__Canon, mscorlib]](IBatisNet.DataMapper.Scope.RequestScope, IBatisNet.DataMapper.ISqlMapSession, System.Object, System.Collections.Generic.IList`1<System.__Canon>, IBatisNet.DataMapper.RowDelegate`1<System.__Canon>)
  4. ebp+90: 471be6c4
  5. -> 32c2ea50 System.Collections.Generic.List`1[[xxx.Model]]
  6. -> 53391010 xxxRMT[]
  7. -> 4c0507c0 xxxMT
  8. 0:048> !do 32c2ea50
  9. Name: System.Collections.Generic.List`1[[UMES.MESClient.DBHelper.Model.MPRMT, UMES.MESClient.DBHelper.Model]]
  10. MethodTable: 095f1b58
  11. EEClass: 6e246b4c
  12. Size: 24(0x18) bytes
  13. File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
  14. Fields:
  15. MT Field Offset Type VT Attr Value Name
  16. 6e67ca34 4001886 4 System.__Canon[] 0 instance 53391010 _items
  17. 6e66f2d8 4001887 c System.Int32 1 instance 3011824 _size
  18. 6e66f2d8 4001888 10 System.Int32 1 instance 3011824 _version
  19. 6e66d824 4001889 8 System.Object 0 instance 00000000 _syncRoot
  20. 6e67ca34 400188a 4 System.__Canon[] 0 static <no information>

从卦中看当前的List有length=3011824,并且还被 89c 线程持有,最终通过代码找到了是某种查询下导致的大SQL引发。

三:总结

这次程序卡死还是挺有意思的,表象是主线程被GC卡住,实则是大SQL导致虚拟地址不足,分享出来让大家少踩坑吧!

记一次 .NET某MES自动化桌面程序 卡死分析的更多相关文章

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

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

  2. 记一次 .NET 某金融企业 WPF 程序卡死分析

    一:背景 1. 讲故事 前段时间遇到了一个难度比较高的 dump,经过几个小时的探索,终于给找出来了,在这里做一下整理,希望对大家有所帮助,对自己也是一个总结,好了,老规矩,上 WinDBG 说话. ...

  3. 记一次 .NET 某企业OA后端服务 卡死分析

    一:背景 1.讲故事 前段时间有位朋友微信找到我,说他生产机器上的 Console 服务看起来像是卡死了,也不生成日志,对方也收不到我的httpclient请求,不知道程序出现什么情况了,特来寻求帮助 ...

  4. 记一次 .NET某医疗器械清洗系统 卡死分析

    一:背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧. 二:WinDbg 分析 1. 为什么会卡死 因为是窗体程 ...

  5. 迁移桌面程序到MS Store(2)——Desktop App Converter

    迁移传统桌面程序到MS Store的另一种方式是使用Desktop App Converter工具.虽然本篇标题包含了Desktop App Converter(以下简称DAC),实际上我是来劝你别用 ...

  6. nw.js桌面程序自动更新(node.js表白记)

    Hello Google Node.js 一个基于Google V8 的JavaScript引擎. 一个伟大的端至端语言,或许我对你的热爱源自于web这门极富情感的技术吧! 注: 光阴似水,人生若梦, ...

  7. 记一次 .NET 某纺织工厂 MES系统 API 挂死分析

    一:背景 1. 讲故事 这个月中旬,有位朋友加我wx求助他的程序线程占有率很高,寻求如何解决,截图如下: 说实话,和不同行业的程序员聊天还是蛮有意思的,广交朋友,也能扩大自己的圈子,朋友说他因为这个b ...

  8. 通过内核对象在服务程序和桌面程序之间通信的小问题 good

    关于在通过 事件对象 在服务程序和普通桌面应用程序相互之间通信的问题,分类情况进行讨论:1.普通桌面应用程序中创建事件,服务程序中打开事件 XP的情况普通桌面应用程序中创建: m_hEvent = : ...

  9. 在桌面程序上和Metro/Modern/Windows store app的交互(相互打开,配置读取)

    这个标题真是取得我都觉得蛋疼..微软改名狂魔搞得我都不知道要叫哪个好.. 这边记录一下自己的桌面程序跟windows store app交互的过程. 由于某些原因,微软的商店应用的安全沙箱导致很多事情 ...

  10. 关于xfce桌面程序启动失败

    当双击桌面图标的时候,出现如下错误信息:Process org.xfce.FileManager exited with status 1 于是做出如下尝试: 1. ps aux | grep Fil ...

随机推荐

  1. 两款轻便且功能强大的gif截取工具 [ScreenToGif] 和 [GifCam]

    轻便且强大 提示 下述工具下载链接为官方或github地址,可能会由于你懂得的原因,而无法打开. 一.ScreenToGif 软件简介: ScreenToGif 也是一款非常轻便的.完全免费的.没广告 ...

  2. 题解 P4819

    前言: 看到目前的题解当中没有并查集做法,于是写一篇水水. 题目描述: 给定一张图,一个图中有黑白两种颜色,已知黑色的点有且只有一个,且每个点是黑色的概率相等,然后点 \(u\) 与点 \(v\) 之 ...

  3. 【随手记录】Llama Tutorial 大语言模型实践

    这个tutorial的契机是yy突然看到了一个workshop 所以类似于一周大作业的形式,输入command输出使用了自动驾驶哪些模块,代码在这里 所以就干一干,顺便写一个tutorial给大家参考 ...

  4. 面向对象java前三次pta作业

    目录: 1.前言 2.设计与分析 3.踩坑心得 4.主要困难及改进建议 5.总结 1.前言 面向对象程序设计(Object-Oriented Programming,简称OOP)是一种编程范式,它以对 ...

  5. NOIP2023 游寄

    NOIP2023 游寄 Day -2 遗憾生病离场回家. Day -1 速度赶往杭州,稍作复习. Day 1 正式开寄. 开题后,发现把所有题看了一遍,一如既往的又臭又长. T3 和 T4 感觉很不可 ...

  6. 视觉差缓动效果的轮播--React版

    React实现视觉差效果缓动轮播 效果如下(图片帧率低看起来有点卡顿,看个大概就行): 分享一下思路: 1.正常引入一个轮播组件(站在巨人肩膀省时省力),去除指示点.引导箭头等不需要的元素,有些组件支 ...

  7. python01-基础概念与环境搭建

    学习目标 了解硬件 & 操作系统 & 软件(应用系统)之间的关系. 了解常见的操作系统都有哪些. 了解编译器和解释器的区别和作用. 了解编程语言进行分类 了解Python解释器的种类 ...

  8. 不会这5个Excel函数,别说你会做数据分析?

    当涉及数据分析时,Excel是一个非常有用的工具,而掌握一些核心函数将大大提高你在数据处理和分析方面的能力.以下是我对五个重要的Excel函数的详细介绍: 1. VLOOKUP 函数 VLOOKUP ...

  9. 叮咚,你的微信年度聊天报告请查收「GitHub 热点速览」

    本周热点项目 WeChatMsg 是一个微信记录提取工具,据说它还能帮你分析聊天记录.生成你的年度聊天报告.而又到了年底,部分不幸的小伙伴要开始写年度总结了,这时候 self-operating-co ...

  10. 基于Browscap对浏览器工具类优化

    项目背景 原有的启动平台公共组件库comm-util的浏览器工具类BrowserUtils是基于UserAgentUtils的,但是该项目最后一个版本发布于 2018/01/24,之至今日23年底,已 ...