一:背景

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. KVM 下如何关闭 virbr0

    安装KVM 后都会发现网络接口里多了一个叫做 virbr0 的虚拟网络接口: 这是由于安装和启用了 libvirt 服务后生成的,libvirt 在服务器(host)上生成一个 virtual net ...

  2. 6.使用nexus3配置yum私有仓库

    yum相较于rpm,能够更好地解决安装软件时的依赖包问题,使用yum安装更简单更方便.搭建本地YUM源服务器,可以避免升级安装软件时占用公网带宽:有了本地YUM源服务器,可以解决无法连接Interne ...

  3. 10_SpringBoot更加详细

    一. 原理初探 1.1 自动装配 1.1.1 pom.xml spring-boot-dependencies: 核心依赖在父工程中 我们在写入或者引入一些SpringBoot依赖的时候, 不需要指定 ...

  4. 跳过nsis Error强制运行程序

    NSIS编译的程序在运行之初,都会有一个程序完整性的自我检测,一旦检测到数据不完整,比如文件资源被篡改,下载过程中有数据丢失等情况,就会报错停止运行,也就是大名鼎鼎的nsis Error  ,其实想跳 ...

  5. Python 实验报告(第三周)

    一.实验目的和要求 1.熟练运用常见选择结构: 2.熟练运用for循环和while循环: 3.理解带else语句的循环结构执行过程和break.continue语句在循环中的作用. 二.实验环境 软件 ...

  6. liunx文件定期本地备份、异地备份、删除备份脚本

    导航 一.背景二.依赖功能介绍三.本地备份脚本四.异地备份脚本五.定期删除备份六.github脚本地址 - - - - - - - - - - 分割线 - - - - - - - - - - 一.背景 ...

  7. hive之数据导入导出

    hive数据导入导出 一.导入数据4种方式 建表语句 create table test( name string, friends array, children map<string, in ...

  8. 华为路由器vrrp(虚拟路由器冗余协议)基本配置命令

    vrrp(虚拟路由器冗余协议)基本配置 int g0/0/0 vrrp vrid 1 virtual-ip 172.16.1.254 创建VRRP备份组,备份组号为1,配置虚拟IP为172.16.1. ...

  9. Spring的自动装配和注解

    Bean的自动装配 自动装配说明 自动装配是使用spring满足bean依赖的一种方法 spring会在应用上下文中为某个bean寻找其依赖的bean. Spring的自动装配需要从两个角度来实现,或 ...

  10. 《上传那些事儿之Nest与Koa》——文件格式怎么了!

    转载自:juejin - 马睿不想说话 概要 本文主要针对在使用node作为服务端接口时,前端上传上传文件至node作为中转,再次上传至oss/cdn的场景.以及针对在这个过程中,需要对同一个文件进行 ...