一:背景

1.讲故事

上个月 .NET调试训练营 里的一位老朋友给我发了一个 8G 的dump文件,说他的程序内存泄露了,一时也没找出来是哪里的问题,让我帮忙看下到底是怎么回事,毕竟有了一些调试功底也没分析出来,说明还是有一点复杂的,现实世界中的dump远比课上说的复杂的多。

还是那句话,找我分析是免费的,没有某软高额的工时费,接下来我们上 WinDbg 说话。

二:WinDbg 分析

1. 托管还是非托管泄露

这是我们首先就要做出的抉择,否则就会南辕北辙,可以使用 !address -summary & !eeheap -gc 来定位一下。


0:000> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 1311 7ffc`e2b37000 ( 127.988 TB) 99.99%
<unknown> 4799 2`4f798000 ( 9.242 GB) 74.19% 0.01%
Heap 3029 0`906fe000 ( 2.257 GB) 18.12% 0.00%
Image 3435 0`2b530000 ( 693.188 MB) 5.43% 0.00%
Stack 226 0`11e00000 ( 286.000 MB) 2.24% 0.00%
Other 90 0`0025c000 ( 2.359 MB) 0.02% 0.00%
TEB 75 0`00096000 ( 600.000 kB) 0.00% 0.00%
PEB 1 0`00001000 ( 4.000 kB) 0.00% 0.00% --- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE 7990 2`e6964000 ( 11.603 GB) 93.14% 0.01%
MEM_IMAGE 3445 0`2b536000 ( 693.211 MB) 5.43% 0.00%
MEM_MAPPED 220 0`0b61f000 ( 182.121 MB) 1.43% 0.00% --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 1311 7ffc`e2b37000 ( 127.988 TB) 99.99%
MEM_COMMIT 8158 1`cf52a000 ( 7.239 GB) 58.11% 0.01%
MEM_RESERVE 3497 1`4df8f000 ( 5.218 GB) 41.89% 0.00% 0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000023ba303e940
generation 1 starts at 0x0000023ba2ebd0d0
generation 2 starts at 0x00000239a80f1000
ephemeral segment allocation context: none
...
Large object heap starts at 0x00000239b80f1000
segment begin allocated size
00000239b80f0000 00000239b80f1000 00000239bfe174a8 0x7d264a8(131228840)
0000023a6f050000 0000023a6f051000 0000023a73780800 0x472f800(74643456)
Total Size: Size: 0xea9878f8 (3935860984) bytes.
------------------------------
GC Heap Size: Size: 0xea9878f8 (3935860984) bytes.

从卦中的 MEM_COMMITGC Heap Size 这两个指标来看,主要还是托管内存泄露,虽然非托管内存也不小,大概率还是托管这边导致的,有了这些信息之后,后面就是看下 托管堆 到底都是些什么对象。


0:000> !dumpheap -stat
Statistics:
MT Count TotalSize Class Name
...
00007ffa2d7a1080 4923008 118152192 System.WeakReference
00007ffa2d725e70 2224022 125834760 System.Object[]
00007ffa2701de10 1044218 133659904 System.Windows.Documents.Paragraph
00007ffa2706b470 1045023 142123128 System.Windows.Documents.Run
00007ffa2706a9b0 2098480 151090560 System.Windows.Documents.TextTreeTextNode
00007ffa2d7267d0 1138661 159949302 System.Char[]
00007ffa2d7259c0 1231039 160962948 System.String
00007ffa29580cd8 214 165608376 MS.Internal.WeakEventTable+EventKey[]
00007ffa2d729750 2116556 169324480 System.Collections.Hashtable
00007ffa2d724478 2117718 209740224 System.Collections.Hashtable+bucket[]
00007ffa2706eb08 4175733 367464504 System.Windows.Documents.TextTreeTextElementNode
00007ffa2700ca48 2088016 384194944 System.Windows.ResourceDictionary
00007ffa2957fdc8 2344569 405666920 System.Windows.EffectiveValueEntry[]

从卦中的 TotalSize 来看并没有明显的特征,但从 Count 看还是有一些蛛丝马迹的,比如 System.Windows.Documents.TextTreeTextElementNode 对象为什么高达 417w ? 为什么 System.Windows.Documents.TextTreeTextNode209w ? 虽然都是 WPF 框架的内部类,但从名字上看貌似和 文本类 控件有关系。

2. TextTreeTextElementNode 为什么没被回收

有了这些可疑信息,接下来就需要看下他们为什么没有被 GC 收掉?要想找到答案就需要抽几个 TextTreeTextElementNode 看下用户根是什么?可以使用 !dumpheap -mt xxx 找到 address 之后再用 !gcroot 观察一下。


0:000> !dumpheap -mt 00007ffa2706eb08
Address MT Size
00000239a815f028 00007ffa2706eb08 88
00000239a815f080 00007ffa2706eb08 88
00000239a815f2e8 00007ffa2706eb08 88
00000239a815f340 00007ffa2706eb08 88
00000239a8259f18 00007ffa2706eb08 88
... 0:000> !gcroot 0000023a637180e0
!gcroot 0000023a637180e0
Thread e6c:
000000aebe7fec20 00007ffa296c0298 System.Windows.Threading.Dispatcher.GetMessage(System.Windows.Interop.MSG ByRef, IntPtr, Int32, Int32)
rsi:
-> 00000239a8101688 System.Windows.Threading.Dispatcher
-> 0000023b4630e9a8 System.EventHandler
-> 0000023b4630a990 System.Object[]
-> 00000239a8425648 System.EventHandler
...

结果刷了半天都没刷完,还把 windbg 给弄死了,看样子这个引用链得有几十万哈。。。截图如下:

那遇到这种情况怎么办呢? 为了能够记录到所有的引用链,大家可以用 windbg 的 .logopen.logclose 命令将所有的输出记录到文本中,喝了杯咖啡之后,终于output完了,看文件有 81w 行,真的心累。

一眼望去大多是 TextTreeTextElementNode 和 TextTreeFixupNode 之间的交叉引用,还得耐点心慢慢往上翻,看看可有什么蛛丝马迹,经过仔细排查,发现有一个 RickTextBox 控件,截图如下:

从名字上来看,可能是想用 RichTextBox 记录日志,接下来看下 OperatorLogItemRichTextBox 这个类是怎么写的。


public sealed class OperatorLogItemRichTextBox : RichTextBox, IOperatorLogger
{
private static readonly DependencyProperty MaximumLogCountProperty = DependencyProperty.Register("MaximumLogCount", typeof(int), typeof(OperatorLogItemRichTextBox), new PropertyMetadata(1024)); private static readonly DependencyProperty VerboseBrushProperty = DependencyProperty.Register("VerboseBrush", typeof(Brush), typeof(OperatorLogItemRichTextBox), new PropertyMetadata(Brushes.Gray)); private static readonly DependencyProperty DebugBrushProperty = DependencyProperty.Register("DebugBrush", typeof(Brush), typeof(OperatorLogItemRichTextBox), new PropertyMetadata(Brushes.Cyan)); ... private static readonly DependencyProperty ExceptionBrushProperty = DependencyProperty.Register("ExceptionBrush", typeof(Brush), typeof(OperatorLogItemRichTextBox), new PropertyMetadata(Brushes.Magenta)); private static readonly DependencyProperty SpecialBrushProperty = DependencyProperty.Register("SpecialBrush", typeof(Brush), typeof(OperatorLogItemRichTextBox), new PropertyMetadata(Brushes.Magenta)); ...
}

从源码看,朋友在项目中实现了一个自定义的 RichTextBox 控件来实现日志记录,内存泄露问题应该就在这里。

有车的朋友都知道 4S 店有一个好的传统,那就是 只换不修,又简单又能轻松挣钱,所以我给朋友的建议是:把 OperatorLogItemRichTextBox 从项目中给踢掉,排查下还有没有内存泄露的问题。

终于在一周后,收到了朋友的反馈,问题也终于解决了,截图如下:

三: 总结

其实关于 RichTextBox 的问题我遇到过二次,上次是崩溃相关的,如果要用它来记录日志,建议还是用信得过的第三方富文本控件,自己实现的话,难免会踩到很多坑。

记一次 .NET 某工控软件 内存泄露分析的更多相关文章

  1. 记一次 .NET 某工控自动化控制系统 卡死分析

    一:背景 1. 讲故事 前段时间遇到了好几起关于窗体程序的 进程加载锁 引发的 程序卡死 和 线程暴涨 问题,这种 dump 分析难度较大,主要涉及到 Windows操作系统 和 C++ 的基础知识, ...

  2. 记一次Java的内存泄露分析

    当前环境 jdk == 1.8 httpasyncclient == 4.1.3 代码地址 git 地址:https://github.com/jasonGeng88/java-network-pro ...

  3. 记一次 .NET 某工控视觉软件 非托管泄漏分析

    一:背景 1.讲故事 最近分享了好几篇关于 非托管内存泄漏 的文章,有时候就是这么神奇,来求助的都是这类型的dump,一饮一啄,莫非前定.让我被迫加深对 NT堆, 页堆 的理解,这一篇就给大家再带来一 ...

  4. 记一次 .NET 某工控数据采集平台 线程数 爆高分析

    一:背景 1. 讲故事 前几天有位朋友在 B站 加到我,说他的程序出现了 线程数 爆高的问题,让我帮忙看一下怎么回事,截图如下: 说来也奇怪,这些天碰到了好几起关于线程数无缘无故的爆高,不过那几个问题 ...

  5. 记一次 .NET 某外贸Web站 内存泄漏分析

    一:背景 1. 讲故事 上周四有位朋友加wx咨询他的程序内存存在一定程度的泄漏,并且无法被GC回收,最终机器内存耗尽,很尴尬. 沟通下来,这位朋友能力还是很不错的,也已经做了初步的dump分析,发现了 ...

  6. 记一次 .NET 某电厂Web系统 内存泄漏分析

    一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5 在内存管理方面做的也不够好,所以有必要给大家分享 ...

  7. 工控随笔_08_西门子_Win10安装Step7.V5.6中文版授权管理器不能正常启动

    随着Windows系统的不断升级,西门子工控软件也不断升级,但是有时候在安装西门子 软件的时候会出现授权管理器不能正常启动的情况. 图  Step7 因为自动许可证管理器不能正常打开 如上图所示,报S ...

  8. 两款工控控件对比评测:Iocomp和ProEssentials

    对于程序员来说,要凭一己之力开发出漂亮逼真的工控仪表和工控图表是非常耗时间和精力的,那么使用专业的第三方控件就是不错的选择,不仅节约开发时间,降低了项目风险,最重要的是第三方控件写的程序更专业,工控图 ...

  9. 工控随笔_24_关于西门子Step7的Simatic manager打开报3280:503错误。

    微软推出Win10系统后,很多工控软件也被迫跟着升级,但是因为Win10系统的不稳定性,导致很多时候,安装的软件莫名其妙的 不能用. 相对Win7和WinXP来说,Win10在兼容性和稳定性都差很多. ...

  10. 开源纯C#工控网关+组态软件

    一.   前言 在园子潜水也七八年了.说来惭愧,这么多年虽然一直自称.NET铁杆粉丝,然仅限于回几个不痛不痒的贴,既没有发布过代码,也没有写过文章. 看着.NET和C#在国外风生水起,国内却日趋没落, ...

随机推荐

  1. Elasticsearch:Elasticsearch SQL介绍及实例 (一)

    转载自:https://blog.csdn.net/UbuntuTouch/article/details/105658911

  2. krew插件安装

    概念 Krew是kubectl插件的包管理工具.借助Krew,可以轻松地使用kubectl plugin:发现插件.安装和管理插件.使用类似apt.dnf或者brew. 对于kubectl用户:kre ...

  3. filebeat直接给es传输日志,自定义索引名,自定义多个索引文件

    官方文档地址: https://www.elastic.co/guide/en/beats/filebeat/7.3/elasticsearch-output.html https://www.ela ...

  4. Gitlab注册Runner

    1.先启动Gitlab,然后登陆进去,找到项目设置界面 2.部署Runner 这里采用docker安装的方式,也可以采用其他方式安装 # 创建docker镜像使用的数据卷 {20-07-16 16:2 ...

  5. 利用python对websocket进行并发压测

    简述 产品经理鉴于运营反馈并对程序的websocket长连接保持怀疑的态度,让我对websocket服务器进行压力测试,我内心是拒绝的. 开发思路 查阅websocket的相关资料,查到python的 ...

  6. WMS 相比于 ERP 系统有哪些优势?

    WMS与ERP系统是两个不同的系统,不存储优势的比较!WMS是仓库管理系统(Warehouse Management System) 的缩写,ERP是Enterprise Resource Plann ...

  7. 分布式存储系统之Ceph集群存储池、PG 与 CRUSH

    前文我们了解了ceph集群状态获取常用命令以及通过ceph daemon.ceph tell动态配置ceph组件.ceph.conf配置文件相关格式的说明等,回顾请参考https://www.cnbl ...

  8. input框限制只能输入数字的两种方法

    方法一: <input type="text"   oninput="value=value.replace(/[^\d.%]/g,'')"> 方法 ...

  9. Taurus.MVC 微服务框架 入门开发教程:项目部署:7、微服务节点的监控与告警。

    系统目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 开源地址:https://github.com/cyq1162/Taurus.MVC 本系列第一篇:Tauru ...

  10. laravel 报错 AUTH` failed: ERR Client sent AUTH, but no password is set

    明明没有设置redis密码.访问时候却报错 在代码里面的databases.php 改成这样就可以了.predis新版也会有取不到passwor的时候.改成我截图那样也可以.他默认取的是default ...