一:背景

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 某电厂Web系统 内存泄漏分析的更多相关文章

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

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

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

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

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

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

  4. 记一次 .NET 某旅行社Web站 CPU爆高分析

    一:背景 1. 讲故事 前几天有位朋友wx求助,它的程序内存经常飙升,cpu 偶尔飙升,没找到原因,希望帮忙看一下. 可惜发过来的 dump 只有区区2G,能在这里面找到内存泄漏那真有两把刷子..., ...

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

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

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

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

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

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

  8. 记一次 .NET 某HIS系统后端服务 内存泄漏分析

    一:背景 1. 讲故事 前天那位 his 老哥又来找我了,上次因为CPU爆高的问题我给解决了,看样子对我挺信任的,这次另一个程序又遇到内存泄漏,希望我帮忙诊断下. 其实这位老哥技术还是很不错的,他既然 ...

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

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

随机推荐

  1. XCTF练习题---MISC---适合作为桌面

    XCTF练习题---适合作为桌面 flag:flag{38a57032085441e7} 解题步骤: 1.观察题目,下载附件 2.拿到题目以后是一张图片,切换一下通道,发现一张二维码,使用QR进行翻译 ...

  2. XCTF练习题---MISC---embarrass

    XCTF练习题---MISC---embarrass flag:flag{Good_b0y_W3ll_Done} 解题步骤: 1.观察题目,下载附件,这道题真难 2. 拿到手以后默认使用Wiresha ...

  3. Kubernetes生产环境最佳实践

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 众所周知,Kubernetes很难! 以下是在生产中使用 ...

  4. Linux应急响应入门——入侵排查

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 账号安全: 1.用户信息文件 /etc/passwd # ...

  5. 中国电子云数据库 Mesh 项目 DBPack 的实践

    作者:刘晓敏 2022 年 4 月,中国电子云开源了其云原生数据库 Mesh 项目 DBPack.该项目的诞生,旨在解决用户上云过程中面临的一些技术难点,诸如分布式事务.分库分表等.由于它数据库 Me ...

  6. nodejs + typescript + koa + eslint + typescript eslint + prettier + webstorm

    ESLint 安装 yarn add -D eslint 生成配置文件 yarn eslint --init cli 选项 How would you like to use ESLint? To c ...

  7. 零基础学Java第三节(基本输入输出)

    本篇文章是<零基础学Java>专栏的第三篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! 本文章首发于公众号[编程攻略] Java程序的命令行参数 我们可以 ...

  8. 好客租房10-jsx的基本使用

    1.1createElement()的问题 1繁琐不简洁 2不直观 无法一眼看出所描述的结构 3不优雅 用户体验不爽 React.createElement("div",     ...

  9. 149_1秒获取Power BI Pro帐号

    一.背景 当你来到这篇文章的时候,我想你已经在网上搜索了一圈了.网上有一大把教你如何注册Power BI帐号的方法,我们这里就不在赘述了.因为各种因素的限制确实比较麻烦.我们直接提供Power BI ...

  10. 关于ECharts图表反复修改都无法显示的解决方案

    解决方案:清空浏览器所有记录,再次刷新即可