记一次.net某电厂web系统内存泄漏分析(代码片段)

dotNET跨平台 dotNET跨平台     2023-03-14     647

关键词:

一:背景

1. 讲故事

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

二:WinDbg 分析

1. 托管还是非托管泄漏

这个还是老规矩 !address -summary!eeheap -gc 组合命令排查一下。

0:000> !address -summary

                                     
Mapping file section regions...
Mapping module regions...
Mapping PEB regions...
Mapping TEB and stack regions...
Mapping heap regions...
Mapping page heap regions...
Mapping other regions...
Mapping stack trace database regions...
Mapping activation context regions...
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                426     7df8`af1ce000 ( 125.971 TB)           98.42%
MEM_RESERVE                             619      206`01b9c000 (   2.023 TB)  99.75%    1.58%
MEM_COMMIT                             3096        1`4f286000 (   5.237 GB)   0.25%    0.00%

0:000> !eeheap -gc
Number of GC Heaps: 16
------------------------------
...
Heap 15 (0000024AF6BAA2E0)
generation 0 starts at 0x000002509729B538
generation 1 starts at 0x000002509720B638
generation 2 starts at 0x0000025096F91000
ephemeral segment allocation context: none
         segment             begin         allocated         committed    allocated size    committed size
0000025096F90000  0000025096F91000  000002509B5AFB40  000002509DFE9000  0x461eb40(73526080)  0x7058000(117800960)
Large object heap starts at 0x00000250D6F91000
         segment             begin         allocated         committed    allocated size    committed size
00000250D6F90000  00000250D6F91000  00000250DEB6AC60  00000250DEB6B000  0x7bd9c60(129866848)  0x7bda000(129867776)
Pinned object heap starts at 0x00000250E6F91000
00000250E6F90000  00000250E6F91000  00000250E75D94E0  00000250E75DA000  0x6484e0(6587616)  0x649000(6590464)
Allocated Heap Size:       Size: 0xc840c80 (209980544) bytes.
Committed Heap Size:       Size: 0xec32000 (247668736) bytes.
------------------------------
GC Allocated Heap Size:    Size: 0xd6904dd8 (3599781336) bytes.
GC Committed Heap Size:    Size: 0x11884b000 (4706316288) bytes.

从卦中指标看:5.2G4.7G ,很明显问题出在了托管层,但如果你细心的话,你会发现这 4.7G 是 commit 内存,其实真正占用的只有 3.5G,言外之意有 1.2G 的空间其实属于 Commit 区,也就是为了少向 OS 申请内存而虚占的一部分空间,画个简图就像下面这样:

这也是我第一次看到 AllocCommit 差距有这么大。

2. 探究托管内存占用

首先看下 3.5G 内存这块,这个分析比较简单,直接看托管堆就好了。

0:000> !dumpheap -stat
Statistics:
              MT    Count    TotalSize Class Name
...
00007ffa19e64808    25804     36125600 xxxx.MongoDB.Entity.GeneratorMongodb
0000024af68aa2c0    20517    630474976      Free
00007ffa1947bf30    52477    654558722 System.Byte[]
00007ffa194847f0     1921   1044818774 System.Char[]
00007ffa19437a90   673850   1116597742 System.String

从输出信息看,主要还是被 String,Char[],Byte[] 占用了,根据经验,这三个组合在一块,大多是存了什么字节流在内存中,比如 PdfImage ,然后在内存中倒来倒去就成这个样子了。

接下来在 char[] 中抽一些 obj 看一下,果然大多是 jpg

0:000> !DumpObj /d 00000250da9d3618
Name:        System.Char[]
MethodTable: 00007ffa194847f0
EEClass:     00007ffa19484770
Size:        11990052(0xb6f424) bytes
Array:       Rank 1, Number of elements 5995014, Type Char (Print Array)
Content:     data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAASwCAYAAACjAoQOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DA
Fields:
None
0:000> !DumpObj /d 00000250db542a60
Name:        System.Char[]
MethodTable: 00007ffa194847f0
EEClass:     00007ffa19484770
Size:        15667860(0xef1294) bytes
Array:       Rank 1, Number of elements 7833918, Type Char (Print Array)
Content:     data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAA4QAAASwCAYAAACjAoQOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DA
Fields:
None

可以看到,3.2G 的内存大多是被 图片 所占用,朋友反馈是把 图片 存到数据库所致,好了,这一块就分析到这里,分析思路也很明显,接下来探究下 alloc 和 commit 的问题。

3. 为什么 alloc 和 commit 差距这么大

一般而言,差距大有以下几点诱因所致。

  1. segment 越大,commit 预设的区域就越大

根据官方文档的定义,segment 的大小取决于 cpu核数 和 程序的位数,截图如下:

有了这个指标,怎么到 dump去找各自数据呢,用 !eeversion 看下 heap 的个数以及观察下内存地址的长度就好啦。

0:000> !eeversion
5.0.621.22011 free
5,0,621,22011 @Commit: 478b2f8c0e480665f6c52c95cd57830784dc9560
Server mode with 16 gc heaps
SOS Version: 6.0.5.7301 retail build

可以看到,这个程序是用 64bit 跑在 16 核机器上,segment 上限为 1G

  1. segment 越多,alloc 和 commit 累计差距就会越大

每个 segment 都差一点,那多个 segment 自然就累计出来了,接下来就找一下那些差距比较大的 segment。

Heap 0 (0000024AF685A500)
         segment             begin         allocated         committed    allocated size    committed size
0000024AF6F90000  0000024AF6F91000  0000024AF83B6D28  0000024AFEB42000  0x1425d28(21126440)  0x7bb1000(129699840)
------------------------------
Heap 1 (0000024AF68819A0)
         segment             begin         allocated         committed    allocated size    committed size
0000024B56F90000  0000024B56F91000  0000024B58507410  0000024B5D2E5000  0x1576410(22504464)  0x6354000(104153088)
------------------------------
Heap 4 (0000024AF688F770)
         segment             begin         allocated         committed    allocated size    committed size
0000024C76F90000  0000024C76F91000  0000024C783BDBE8  0000024C7ECF7000  0x142cbe8(21154792)  0x7d66000(131489792)
------------------------------
Heap 6 (0000024AF68980A0)
         segment             begin         allocated         committed    allocated size    committed size
0000024D36F90000  0000024D36F91000  0000024D38B87E78  0000024D3F881000  0x1bf6e78(29322872)  0x88f0000(143589376)
...

从输出信息看,差距最大的是 Heap6,高达 110M,那这 110M 差距是否合理呢?其实仔细想想也不太离谱,毕竟命中了上面提到的两点,但我觉得这里的空间是不是还可以再智能的优化一下,再缩小一点?

4. Commit区能不能再小点?

能不能缩的再小一点,其实这是一种 CLR 智能算法的抉择,Commit 区越大,申请对象的速度就越快,向 OS 申请内存的频率就越低,反之 Commit 区越小,向 OS 再次申请内存的概率就越大,段的模型图大概是这个样子:

后来仔细想了下,既然 Commit 区多保留了 110M,那曾经肯定是某一个时刻突破过,后来因为成了垃圾对象,被 GC 回收了,但内存区域被GC私藏下来,所以程序肯定出现过 快出快进 的现象,接下来的想法就是用 writemem 把 alloc ~ commit 的内存区间给导出来看下,是不是有什么新发现。

0:000> .writemem D:\\dumps\\dump1\\1.txt 0000024AF83B6D28 L?0x0678b2d8 
Writing 678b2d8 bytes.............

发现了很多类似这样的信息,把这个信息提供给朋友后,朋友说他找到这块问题了,是网站上用 NPOI  数据导出 功能所致。

三:总结

其实这个 dump 给了我们两方面的教训。

  1. 不要将 image 放到 sqlserver 里,不仅占用sql的资源,让程序也不堪重负,毕竟读出去都是 byte[] ...

  2. coreclr 虽然有自己的抉择算法,如果再智能一点就好了,让 commit ~ alloc 之间的差距更合理一点。

记一次.net某智能服装智造系统内存泄漏分析(代码片段)

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

记一次.net某消防物联网后台服务内存泄漏分析

一:背景1.讲故事去年十月份有位朋友从微信找到我,说他的程序内存要炸掉了。。。截图如下:时间有点久,图片都被清理了,不过有点讽刺的是,自己的程序本身就是做监控的,结果自己出了问题,太尴尬了 查看详情

记一次.net某消防物联网后台服务内存泄漏分析

一:背景1.讲故事去年十月份有位朋友从微信找到我,说他的程序内存要炸掉了。。。截图如下:时间有点久,图片都被清理了,不过有点讽刺的是,自己的程序本身就是做监控的,结果自己出了问题,太尴尬了 查看详情

记一次.net某桌面奇侠游戏非托管内存泄漏分析(代码片段)

一:背景1.讲故事说实话,这篇dump我本来是不准备上一篇文章来解读的,但它有两点深深的感动了我。无数次的听说用Unity可做游戏开发,但百闻不如一见。游戏中有很多金庸武侠小说才有的名字,太赏心悦目... 查看详情

记一次.net某智慧水厂api非托管内存泄漏分析(代码片段)

一:背景1.讲故事七月底的时候有位朋友在wx上找到我,说他的程序内存占用8G,托管才占用1.5G,询问剩下的内存哪里去了?截图如下:从求助内容看,这位朋友真的太客气了,动不动就谈钱,... 查看详情

记一次.net某打印服务非托管内存泄漏(代码片段)

一:背景1.讲故事前段时间有位朋友在微信上找到我,说他的程序出现了内存泄漏,能不能帮他看一下,这个问题还是比较经典的,加上好久没上非托管方面的东西了,这篇就和大家分享一下,话不多... 查看详情

记一次.net某工控视觉软件非托管泄漏分析(代码片段)

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

记一次.net某工控视觉软件非托管泄漏分析(代码片段)

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

记一次.net某消防物联网后台服务内存泄漏分析(代码片段)

一:背景1.讲故事去年十月份有位朋友从微信找到我,说他的程序内存要炸掉了。。。截图如下:时间有点久,图片都被清理了,不过有点讽刺的是,自己的程序本身就是做监控的,结果自己出了问题&#... 查看详情

记一次.net某电商定向爬虫内存碎片化分析(代码片段)

一:背景1.讲故事上个月有位朋友wx找到我,说他的程序存在内存泄漏问题,寻求如何解决?如下图所示:从截图中可以看出,这位朋友对windbg的操作还是有些熟悉的,可能缺乏一定的实操经验,所... 查看详情

记一次.net某wms仓储打单系统内存暴涨分析(代码片段)

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

记一次.net某制造业mes系统崩溃分析(代码片段)

一:背景1.讲故事前段时间有位朋友微信找到我,说他的程序偶尔会出现内存溢出崩溃,让我帮忙看下是怎么回事,咨询了下程序是x86部署,听到这个词其实心里已经有了数,不管怎么样还是用windbg分析一... 查看详情

记一次.net某流媒体独角兽api句柄泄漏分析(代码片段)

一:背景1.讲故事上上周有位朋友找到我,说他的程序CPU和句柄都在不断的增长,无回头趋势,查了好些天也没什么进展,特加wx寻求帮助,截图如下:看的出来这位朋友也是非常郁闷,出问题还出... 查看详情

记一次.net某家装erp系统内存暴涨分析(代码片段)

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

记一次.net某手术室行为信息系统内存泄露分析(代码片段)

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

记一次.net某供应链web网站cpu爆高事故分析(代码片段)

一:背景1.讲故事年前有位朋友加微信求助,说他的程序出现了偶发性CPU爆高,寻求如何解决,截图如下:我建议朋友用procdump在cpu高的时候连抓两个dump,这样分析起来比较稳健,朋友也如期的成功抓到,接下来就用windbg一起来... 查看详情

记一次内存溢出查找分析文档(代码片段)

内存溢出介绍。内存溢出和内存泄漏的联系:内存泄漏会最终导致内存溢出。相同点:都会导致应用程序运行出现问题,性能下降或挂起。不同点:1内存泄漏是导致内存溢出的原因之一,内存泄漏积累起来导... 查看详情

记一次.net某上市工业智造cpu+内存+挂死三高分析(代码片段)

一:背景1.讲故事上个月有位朋友加wx告知他的程序有挂死现象,询问如何进一步分析,截图如下:看这位朋友还是有一定的分析基础,可能玩的少,缺乏一定的分析经验,当我简单分析之后,我发... 查看详情