热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

记一次.NET某电厂Web系统内存泄漏分析-一:背景1.讲故事前段时间有位朋友找到我,说他的程序内存占用比较大,寻求如何解决,截图就不发了,分析下来我感觉除了程序本身的问题之

一:背景

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 之间的差距更合理一点。


推荐阅读
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文详细介绍了MysqlDump和mysqldump进行全库备份的相关知识,包括备份命令的使用方法、my.cnf配置文件的设置、binlog日志的位置指定、增量恢复的方式以及适用于innodb引擎和myisam引擎的备份方法。对于需要进行数据库备份的用户来说,本文提供了一些有价值的参考内容。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文详细介绍了如何使用MySQL来显示SQL语句的执行时间,并通过MySQL Query Profiler获取CPU和内存使用量以及系统锁和表锁的时间。同时介绍了效能分析的三种方法:瓶颈分析、工作负载分析和基于比率的分析。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • MySQL 运行原理 [数据页]
    文章根据《MySQL技术内幕:InnoDB存储引擎(第二版)》、掘金小册《MySQL是怎样运行的:从根儿上理解MySQL》两本书得到的总结文章参考淘宝数据库内核组月报文章中的图均截 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • WhenIusepythontoapplythepymysqlmoduletoaddafieldtoatableinthemysqldatabase,itdo ... [详细]
  • PDO MySQL
    PDOMySQL如果文章有成千上万篇,该怎样保存?数据保存有多种方式,比如单机文件、单机数据库(SQLite)、网络数据库(MySQL、MariaDB)等等。根据项目来选择,做We ... [详细]
  • 技术周报·2021-05-07-小编推荐向现代Javascript转型原文标题:Publish,ship,andinstallmodernJavaScriptforfaste ... [详细]
  • 尾部|柜台_Java并发线程池篇附场景分析
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java并发-线程池篇-附场景分析相关的知识,希望对你有一定的参考价值。作者:汤圆个人博客 ... [详细]
  • 4.3.2Tuple是否可以跨页面PostgreSQLusesafixedpagesize(commonly8kB),anddoesnotallowtuplestospanmult ... [详细]
author-avatar
微笑
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有