记一次 .NET 某工控软件 内存泄露分析
一:背景
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_COMMIT 和 GC 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.TextTreeTextNode 有 209w ? 虽然都是 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 某工控软件 内存泄露分析的更多相关文章
- 记一次 .NET 某工控自动化控制系统 卡死分析
一:背景 1. 讲故事 前段时间遇到了好几起关于窗体程序的 进程加载锁 引发的 程序卡死 和 线程暴涨 问题,这种 dump 分析难度较大,主要涉及到 Windows操作系统 和 C++ 的基础知识, ...
- 记一次Java的内存泄露分析
当前环境 jdk == 1.8 httpasyncclient == 4.1.3 代码地址 git 地址:https://github.com/jasonGeng88/java-network-pro ...
- 记一次 .NET 某工控视觉软件 非托管泄漏分析
一:背景 1.讲故事 最近分享了好几篇关于 非托管内存泄漏 的文章,有时候就是这么神奇,来求助的都是这类型的dump,一饮一啄,莫非前定.让我被迫加深对 NT堆, 页堆 的理解,这一篇就给大家再带来一 ...
- 记一次 .NET 某工控数据采集平台 线程数 爆高分析
一:背景 1. 讲故事 前几天有位朋友在 B站 加到我,说他的程序出现了 线程数 爆高的问题,让我帮忙看一下怎么回事,截图如下: 说来也奇怪,这些天碰到了好几起关于线程数无缘无故的爆高,不过那几个问题 ...
- 记一次 .NET 某外贸Web站 内存泄漏分析
一:背景 1. 讲故事 上周四有位朋友加wx咨询他的程序内存存在一定程度的泄漏,并且无法被GC回收,最终机器内存耗尽,很尴尬. 沟通下来,这位朋友能力还是很不错的,也已经做了初步的dump分析,发现了 ...
- 记一次 .NET 某电厂Web系统 内存泄漏分析
一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之外,.NET5 在内存管理方面做的也不够好,所以有必要给大家分享 ...
- 工控随笔_08_西门子_Win10安装Step7.V5.6中文版授权管理器不能正常启动
随着Windows系统的不断升级,西门子工控软件也不断升级,但是有时候在安装西门子 软件的时候会出现授权管理器不能正常启动的情况. 图 Step7 因为自动许可证管理器不能正常打开 如上图所示,报S ...
- 两款工控控件对比评测:Iocomp和ProEssentials
对于程序员来说,要凭一己之力开发出漂亮逼真的工控仪表和工控图表是非常耗时间和精力的,那么使用专业的第三方控件就是不错的选择,不仅节约开发时间,降低了项目风险,最重要的是第三方控件写的程序更专业,工控图 ...
- 工控随笔_24_关于西门子Step7的Simatic manager打开报3280:503错误。
微软推出Win10系统后,很多工控软件也被迫跟着升级,但是因为Win10系统的不稳定性,导致很多时候,安装的软件莫名其妙的 不能用. 相对Win7和WinXP来说,Win10在兼容性和稳定性都差很多. ...
- 开源纯C#工控网关+组态软件
一. 前言 在园子潜水也七八年了.说来惭愧,这么多年虽然一直自称.NET铁杆粉丝,然仅限于回几个不痛不痒的贴,既没有发布过代码,也没有写过文章. 看着.NET和C#在国外风生水起,国内却日趋没落, ...
随机推荐
- Deployment控制器(pod)更新策略
最小就绪时间: 配置时,用户可以使用Deplpoyment控制器的spec.minReadySeconds属性来控制应用升级的速度.新旧更替过程中,新创建的Pod对象一旦成功响应就绪探测即被视作可用, ...
- 使用nginx代理nexus,不是/根路径
location /nexus/ { proxy_pass http://192.168.0.218:8081/; proxy_set_header Host $host:$server_port; ...
- 查看pod对应的DNS域名
单个pod # kubectl exec redis-pod-0 -n cluster-redis -- hostname -f redis-pod-0.redis-cluster-service.c ...
- MongoDB分片集群中配置参数说明
replication: #副本集的名称 replSetName: myshardrs01 sharding: #分片角色 clusterRole: shardsvr sharding.clust ...
- MySQL数据库安装保姆教程及问题解决
使用Mysql的zip压缩包解压版,下载之后需进行一定的配置,才能使用它. 下面对Mysql压缩包版的安装方法进行详细的描述,如有疑问或错误,望及时反馈. 首先,mysql的官方下载地址点我进行下载 ...
- PAT (Basic Level) Practice 1013 数素数 分数 20
令 Pi 表示第 i 个素数.现任给两个正整数 M≤N≤104,请输出 PM 到 PN 的所有素数. 输入格式: 输入在一行中给出 M 和 N,其间以空格分隔. 输出格式: 输出从 PM 到 ...
- 适合生产制造企业用的ERP系统有哪些?
什么叫适合?匹配很重要!同是生产制造企业,规模不同.行业不同.发展阶段不同.生产模式不同.管理理念不同,适用的ERP自然也不同.不同规模的企业选用不同的系统,如过你是大型企业,业务管理都比较标准规范, ...
- 改善C#程序的方法-1 操作字符串
正确操作字符串 引言: 字符串是使用很频繁的一种数据类型. 如果使用不慎,则会为一次字符串操作所带来的额外性能开销而付出代价. 下面从这几个方面来探讨如何正确操作字符串: 1.确保尽量少的装箱,尽可能 ...
- 【疫情动态条形图】用Python开发全球疫情排名动态条形图bar_chart_race
一.开发背景 你好,我是 @马哥python说 ,这是我用Python开发的全球疫情动态条形图,演示效果: https://www.zhihu.com/zvideo/15603276220259696 ...
- react.js+easyui 做一个简单的商品表
效果图: import React from 'react'; import { Form, FormField, Layout,DataList,LayoutPanel,Panel, Lab ...