一:背景

1. 讲故事

前些天有位朋友微信找到我,说他们的WPF程序有内存泄漏的情况,让我帮忙看下怎么回事?并且dump也抓到了,网上关于程序内存泄漏,内存暴涨的文章不计其数,看样子这个dump不是很好分析,不管怎么说,上 windbg 说话。

二:WinDbg分析

1. 内存真的暴涨吗

.NET调试训练营中我一直强调要相信数据,不要相信别人的一面之词,往往会把你带到沟里去,接下来使用 !address -summary 观察下提交内存。


0:000> !address -summary --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 586 7dfd`f04e3000 ( 125.992 TB) 98.43%
<unknown> 1390 201`5a9bc000 ( 2.005 TB) 99.86% 1.57%
Heap 3989 0`7695c000 ( 1.853 GB) 0.09% 0.00%
Image 1744 0`2077d000 ( 519.488 MB) 0.02% 0.00%
Stack 957 0`1dc00000 ( 476.000 MB) 0.02% 0.00%
TEB 319 0`0027e000 ( 2.492 MB) 0.00% 0.00%
Other 61 0`001f9000 ( 1.973 MB) 0.00% 0.00%
PEB 1 0`00001000 ( 4.000 kB) 0.00% 0.00%
...
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 586 7dfd`f04e3000 ( 125.992 TB) 98.43%
MEM_RESERVE 2028 201`46def000 ( 2.005 TB) 99.85% 1.57%
MEM_COMMIT 6433 0`c8d1e000 ( 3.138 GB) 0.15% 0.00%
...

从卦中可知当前的提交内存是 3.1G,对于一个窗体程序来说这个内存量算是比较大了,接下来使用 !eeheap -gc 观察下托管堆内存。


0:000> !eeheap -gc ========================================
Number of GC Heaps: 1
----------------------------------------
generation 0 starts at 1b368e4de10
generation 1 starts at 1b3687ea4f0
generation 2 starts at 1b300001000
ephemeral segment allocation context: none
Small object heap
segment begin allocated committed allocated size committed size
01b300000000 01b300001000 01b30fffff88 01b310000000 0xfffef88 (268431240) 0x10000000 (268435456)
01b35dc70000 01b35dc71000 01b368e8fe28 01b369995000 0xb21ee28 (186773032) 0xbd25000 (198332416)
Large object heap starts at 1b310001000
segment begin allocated committed allocated size committed size
01b310000000 01b310001000 01b316d40560 01b316d41000 0x6d3f560 (114554208) 0x6d41000 (114561024)
01b3cfc50000 01b3cfc51000 01b3d6588320 01b3d6589000 0x6937320 (110326560) 0x6939000 (110333952)
Pinned object heap starts at 1b318001000
segment begin allocated committed allocated size committed size
01b318000000 01b318001000 01b3180812d0 01b318082000 0x802d0 (525008) 0x82000 (532480)
------------------------------
GC Allocated Heap Size: Size: 0x28914900 (680610048) bytes.
GC Committed Heap Size: Size: 0x29421000 (692195328) bytes.

从卦中数据看,当前的托管堆也才 692M,和当前的 3G 相差甚远,这就说明这个程序出现了比较麻烦的 非托管内存泄漏,接下来回头看下内存地址段发现 Heap=1.8G ,有了这个数据后用 !heap -s 观察下地址段。


0:000> !heap -s ************************************************************************************************************************
NT HEAP STATS BELOW
************************************************************************************************************************
LFH Key : 0x3861e2c156213079
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-------------------------------------------------------------------------------------
000001b37a6b0000 00000002 194824 183768 194432 29846 1716 20 30 6fa1 LFH
External fragmentation 16 % (1716 free blocks)
000001b37a4e0000 00008000 64 8 64 6 1 1 0 0
000001b37c140000 00001002 3516 2492 3124 476 69 3 0 0 LFH
External fragmentation 19 % (69 free blocks)
000001b37c380000 00001002 60 36 60 8 3 1 0 0
000001b37c360000 00041002 60 8 60 5 1 1 0 0
000001b37d510000 00001002 1472 88 1080 38 7 2 0 0 LFH
000001b320a10000 00001002 1472 204 1080 71 12 2 0 0 LFH
000001b327a60000 00001002 452 32 60 4 3 1 0 0 LFH
000001b3292b0000 00001002 1513284 1215876 1512892 74984 6445 924 4 2e72c3 LFH
Virtual address fragmentation 19 % (924 uncommited ranges)
Lock contention 3044035
000001b327e80000 00001002 1472 812 1080 439 11 2 0 2 LFH
000001b327cb0000 00001002 3516 1140 3124 519 12 3 0 0 LFH
External fragmentation 45 % (12 free blocks)
000001b327ec0000 00001002 1472 824 1080 468 10 2 0 0 LFH
000001b327cc0000 00001002 1472 1012 1080 441 11 2 0 0 LFH
-------------------------------------------------------------------------------------

从卦中数据看当前的内存都被 Heap=000001b3292b0000 这个私有heap给吃掉了,看样子是某个程序为了某个目的单独分配的,由于没有开启 ust ,这里就没法进行下去了,接下来陷入了迷茫。

2. 在绝望中寻找希望

没有开启ust是不是就没有突破口了呢?大多情况下是的,但作为调试师,需要具备在 绝望中寻找希望 的能力,再回头看地址段,发现 TEB=319,也就说当前程序有 319 个线程,对于一个窗体程序来说这么多线程很明显是一个异常信号,那这个就是突破口,先用 !tp 观察下托管线程列表。

从卦中数据看基本都是线程池的工作线程,为什么会开启这么多线程呢?第一个反应就是线程是不是卡住了?马上用 !syncblk 命令做下验证。


0:000> !syncblk
Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner
2363 000001B3984D6928 381 1 000001B335581A80 607c 135 000001b35e3a0d98 System.Object
-----------------------------
Total 2410
CCW 301
RCW 126
ComClassFactory 1
Free 1783

我去。。。卦中的数据又让我看到了希望!原来有190 个线程卡在 System.Object 锁上,赶紧找个线程观察下线程栈,为了隐私我就多隐藏一点。


0:263> ~~[5a2c]s
ntdll!NtWaitForMultipleObjects+0x14:
00007fff`c800fec4 c3 ret
0:292> !clrstack
OS Thread Id: 0x5a2c (292)
Child SP IP Call Site
0000002E98DFEB48 00007fffc800fec4 [HelperMethodFrame_1OBJ: 0000002e98dfeb48] System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
0000002E98DFECA0 00007fff12dd2ca3 xxx.SqliteHelper.Insert[[System.__Canon, System.Private.CoreLib]](System.__Canon, System.String ByRef)
...
0000002E98DFF220 00007fff136902b6 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread)
0000002E98DFF2D0 00007fff12d1a12b System.Threading.ThreadPoolWorkQueue.Dispatch()
0000002E98DFF360 00007fff136de091 System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart()
0000002E98DFF6B0 00007fff7115aed3 [DebuggerU2MCatchHandlerFrame: 0000002e98dff6b0]

从卦中可以看到当前卡在 SqliteHelper.Insert 方法上,这到底是何方神圣?赶紧看一下代码。



Task.Run 去跑一个异步逻辑,是一个编程大坑,一旦这个 Task.Run 运行比较慢或者前端请求比较大,很容易造成线程饥饿,从这个程序中的 SetBlob 方法来看,就是将 byte[] 丢到 SqlLite 里,所以这个非托管内存泄漏其实是 Sqlite 在非托管层持有的数据。

挖到了根子上的原因之后,解决办法就比较简单了。

  1. 尽量的批量化Insert,不要用 foreach 一条一条的 Insert
  2. 用单独线程队列化处理,不要用偷懒式 Task.Run

三:总结

这次分析之旅是典型的 在绝望中寻找希望,调试者需要具备沉着冷静的心态,坚持不放弃最终在 内存段 的 TEB 上找到了寻找真相的突破口。

记一次 .NET某管理局检测系统 内存暴涨分析的更多相关文章

  1. 记一次 .NET 某三甲医院HIS系统 内存暴涨分析

    一:背景 1. 讲故事 前几天有位朋友加wx说他的程序遭遇了内存暴涨,求助如何分析? 和这位朋友聊下来,这个dump也是取自一个HIS系统,如朋友所说我这真的是和医院杠上了,这样也好,给自己攒点资源, ...

  2. 记一次 .NET 某WMS仓储打单系统 内存暴涨分析

    一:背景 1. 讲故事 七月中旬有一位朋友加wx求助,他的程序在生产上跑着跑着内存就飙起来了,貌似没有回头的趋势,询问如何解决,截图如下: 和这位朋友聊下来,感觉像是自己在小县城当了个小老板,规律的生 ...

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

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

  4. 记一次 .NET某家装ERP系统 内存暴涨分析

    一:背景 1. 讲故事 前段时间微信上有一位老朋友找到我,说他的程序跑着跑着内存会突然爆高,有时候会下去,有什么会下不去,怀疑是不是某些情况下存在内存泄露,让我帮忙分析一下,其实内存泄露方面的问题还是 ...

  5. 记一次 .NET 某医院HIS系统 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友加 wx 抱怨他的程序在高峰期总是莫名其妙的cpu爆高,求助如何分析? 和这位朋友沟通下来,据说这问题困扰了他们几年,还请了微软的工程师过来解决,无疾而终,应该还 ...

  6. 记一次 WinDbg 分析 .NET 某工厂MES系统 内存泄漏分析

    一:背景 1. 讲故事 上个月有位朋友加微信求助,说他的程序跑着跑着就内存爆掉了,寻求如何解决,截图如下: 从聊天内容看,这位朋友压力还是蛮大的,话说这貌似是我分析的第三个 MES 系统了,看样子 . ...

  7. 记一次 .NET 医院CIS系统 内存溢出分析

    一:背景 1. 讲故事 前几天有位朋友加wx求助说他的程序最近总是出现内存溢出,很崩溃,如下图: 和这位朋友聊下来,发现他也是搞医疗的,哈哈,.NET 在医疗方面还是很有市场的,不过对于内存方面出的问 ...

  8. 记一次 .NET 某智能服装智造系统 内存泄漏分析

    一:背景 1. 讲故事 上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下: 朋友这段话已经说的非常言简意赅了,那就上 windbg 说话吧. 二:Windbg 分析 1. ...

  9. 记一次 .NET医疗布草API程序 内存暴涨分析

    一:背景 1. 讲故事 我在年前写过一篇关于CPU爆高的分析文章 再记一次 应用服务器 CPU 暴高事故分析 ,当时是给同济做项目升级,看过那篇文章的朋友应该知道,最后的结论是运维人员错误的将 IIS ...

  10. 记一次 .NET 某手术室行为信息系统 内存泄露分析

    一:背景 1. 讲故事 昨天有位朋友找到我,说他的程序内存存在泄露导致系统特别卡,大地址也开了,让我帮忙看一下怎么回事?今天上午看了下dump,感觉挺有意思,在我的分析之旅中此类问题也蛮少见,算是完善 ...

随机推荐

  1. select_for_update悲观锁

    例子,银行存款和撤销方法 1.用户A提取帐户 - 余额为100 $. 2.用户B提取帐户 - 余额为100 $. 3.用户B退出30 $ - 余额更新为100 $ - 30 $ = 70 $. 4.用 ...

  2. git修改地址三种方法

    1.修改命令 git remote set-url origin [NEW_URL] 2.先删后加 git remote rm origin git remote add origin [url]3. ...

  3. 【Azure 环境】向Azure Key Vault中导入证书有输入密码,那么导出pfx证书的时候,为什么没有密码呢?

    问题描述 将pfx证书导入Key Vault的证书时,这个PFX需要输入正确的密码导入成功.但是当需要导出时,生成的pfx证书则不需要密码.这是正常的情况吗? 问题解答 是的,这是Azure Key ...

  4. 【Azure API 管理】APIM服务资源删除后,为什么不能马上创建相同名称的APIM服务呢?

    问题描述 使用Azure APIM服务,在删除旧资源准备新建相同名称的新APIM服务时,尝试多次都是出现"指定的服务名称已正在使用"错误.但实际上同名称的服务已经被删除.为什么多次 ...

  5. ClickHouse学习笔记--ClickHouse的整体特性

    本文主要包含如下内容: ClickHouse适用场景 ClickHouse缺点 ClickHouse优点 ClickHouse表引擎-合并树 ClickHouse表引擎-合并树-稀疏索引 ClickH ...

  6. C# 操作国产数据库之【 人大金仓 】之四大模式

    人大金仓优点 人大金仓是国产最主流ORM之一 具有和PgSql一样强悍的性能,同时人大金仓支持了四种数据库模式 : Oracle.PgSql.MySql和SqlServer ,假如你们系统有多种数据库 ...

  7. 二十一: Mysql 锁机制

    Mysql 锁机制 事务的 隔离性 由这章讲述的 锁 来实现. 1. 概述 锁是计算机协调多个进程或线程并发访问某一资源的机制.在程序开发中会存在多线程同步的问题,当多个线程并发访问某个数据的时候,尤 ...

  8. 万字博文让我们携手一起走进bs4的世界【python Beautifulsoup】bs4入门 find()与find_all()

    目录 Beautiful Soup BeautifulSoup类的基本元素 1.Tag的name 2.Tag的attrs(属性) 3.Tag的NavigableString 二.遍历文档树 下行遍历 ...

  9. mybatis使用postgresql中的jsonb数据类型

    最近新开发的一个功能使用到postgresql中的jsonb数据类型.架构师可能考虑到这种数据格式更加便于存储json格式的数据,因此考虑使用这种数据类型.自己以前未曾使用过这种数据类型,因此需要现学 ...

  10. PWR & 低功耗三种模式

    睡眠模式: int main(void) { OLED_Init(); OLED_ShowString(1, 1, "RxData:"); Serial_Init(); while ...