记一次 .NET 某外贸Web站 内存泄漏分析
cac55 2024-10-11 10:52 86 浏览 0 评论
一:背景
1. 讲故事
上周四有位朋友加wx咨询他的程序内存存在一定程度的泄漏,并且无法被GC回收,最终机器内存耗尽,很尴尬。
沟通下来,这位朋友能力还是很不错的,也已经做了初步的dump分析,发现了托管堆上有 10w+ 的 byte[]
数组,并占用了大概 1.1G 的内存,在抽取几个 byte[]
的 gcroot 后发现没有引用,接下来就排查不下去了,虽然知道问题可能在 byte[],但苦于找不到证据。
那既然这么信任的找到我,我得要做一个相对全面的输出报告,不能辜负大家的信任哈,还是老规矩,上 windbg 说话。
二: windbg 分析
1. 排查泄漏源
看过我文章的老读者应该知道,排查这种内存泄露的问题,首先要二分法找出到底是托管还是非托管出的问题,方便后续采取相应的应对措施。
接下来使用 !address -summary
看一下进程的提交内存。
||2:2:080> !address -summary
--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE 573 1`5c191000 ( 5.439 GB) 95.19% 0.00%
MEM_IMAGE 1115 0`0becf000 ( 190.809 MB) 3.26% 0.00%
MEM_MAPPED 44 0`05a62000 ( 90.383 MB) 1.54% 0.00%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 201 7ffe`9252e000 ( 127.994 TB) 100.00%
MEM_COMMIT 1477 0`d439f000 ( 3.316 GB) 58.04% 0.00%
MEM_RESERVE 255 0`99723000 ( 2.398 GB) 41.96% 0.00%
从卦象的 MEM_COMMIT
指标看:当前只有 3.3G 的内存占用,说实话,我一般都建议 5G+
是做内存泄漏分析的最低门槛,毕竟内存越大,越容易分析,接下来看一下托管堆
的内存占用。
||2:2:080> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00000002b37c0c48
generation 1 starts at 0x00000002b3781000
generation 2 starts at 0x0000000000cc1000
------------------------------
GC Heap Size: Size: 0xbd322bb0 (3174181808) bytes.
可以看到,当前托管堆占用 3174181808/1024/1024/1024= 2.95G
,哈哈,看到这个数,心里一阵狂喜,托管堆上的问题,对我来说差不多就十拿九稳了。。。毕竟还没有失手过,接下来赶紧排查一下托管堆,看下是哪里出的问题。
2. 查看托管堆
要想查看托管堆,可以使用 !dumpheap -stat
命令,下面我把 Top10 Size
给显示出来。
||2:2:080> !dumpheap -stat
Statistics:
MT Count TotalSize Class Name
00007ffd7e130ab8 116201 13014512 Newtonsoft.Json.Linq.JProperty
00007ffdd775e560 66176 16411648 System.Data.SqlClient._SqlMetaData
00007ffddbcc9da8 68808 17814644 System.Int32[]
00007ffddbcaf788 14140 21568488 System.String[]
00007ffddac72958 50256 22916736 System.Net.Sockets.SocketAsyncEventArgs
00007ffd7deb64b0 369 62115984 System.Collections.Generic.Dictionary`2+Entry[[System.Reflection.ICustomAttributeProvider, mscorlib],[System.Type, mscorlib]][]
00007ffddbcc8610 8348 298313756 System.Char[]
00007ffddbcc74c0 1799807 489361500 System.String
000000000022e250 312151 855949918 Free
00007ffddbccc768 109156 1135674368 System.Byte[]
从上面的输出中可以看到,当前状元是 Byte[]
,榜眼是 Free
,探花是 String
,这里还是有一些经验之谈的,深究 Byte[]
和 String
这种基础类型,投入产出比是不高的,毕竟大量的复杂类型,它的内部结构都含有 String 和 Byte[],比如我相信 MemoryStream 内部肯定有 Byte[],对吧,所以暂且放下状元和探花,看一下榜眼或者其他的复杂类型。
如果你的眼睛犀利,你会发现 Free 的个数有 31W+
,你肯定想问这是什么意思?对,这表明当前托管堆上有 31W+
的空闲块,它的专业术语叫 碎片化
,所以这条信息透露出了当前托管堆有相对严重的碎片化现象,接下来的问题就是为什么会这样? 大多数情况出现这种碎片化的原因在于托管堆上有很多的 pinned 对象,这种对象可以阻止 GC 在回收时对它的移动,长此以往就会造成托管堆的支离破碎,所以找出这种现象对解决泄漏问题有很大的帮助。
补充一下,这里可以借助 dotmemory ,红色表示 pinned 对象,肉眼可见的大量的红色间隔分布,最后的碎片率为 85% 。
接下来的问题是如何找到这些 pinned 对象,其实在 CLR 中有一张 GCHandles 表,里面就记录了这些玩意。
3. 查看 GCHandles
要想找到所有的 pinned 对象,可以使用 !gchandles -stat
命令,简化输出如下:
||2:2:080> !gchandles -stat
Statistics:
MT Count TotalSize Class Name
00007ffddbcc88a0 278 26688 System.Threading.Thread
00007ffddbcb47a8 1309 209440 System.RuntimeType+RuntimeTypeCache
00007ffddbcc7b38 100 348384 System.Object[]
00007ffddbc94b60 9359 673848 System.Reflection.Emit.DynamicResolver
00007ffddb5b7b98 25369 2841328 System.Threading.OverlappedData
Total 36566 objects
Handles:
Strong Handles: 174
Pinned Handles: 15
Async Pinned Handles: 25369
Ref Count Handles: 1
Weak Long Handles: 10681
Weak Short Handles: 326
从卦象中可以看出,当前有一栏为: Async Pinned Handles: 25369
,这表示当前有 2.5w
的异步操作过程中被pinned住的对象,这个指标就相当不正常了,而且可以看出与 2.5W 的System.Threading.OverlappedData
遥相呼应,有了这个思路,可以回过头来看一下托管堆,是否有相对应的 2.5w 个类似封装过异步操作的复杂类型对象? 这里我再把 top10 Size
的托管堆列出来。
||2:2:080> !dumpheap -stat
Statistics:
MT Count TotalSize Class Name
00007ffd7e130ab8 116201 13014512 Newtonsoft.Json.Linq.JProperty
00007ffdd775e560 66176 16411648 System.Data.SqlClient._SqlMetaData
00007ffddbcc9da8 68808 17814644 System.Int32[]
00007ffddbcaf788 14140 21568488 System.String[]
00007ffddac72958 50256 22916736 System.Net.Sockets.SocketAsyncEventArgs
00007ffd7deb64b0 369 62115984 System.Collections.Generic.Dictionary`2+Entry[[System.Reflection.ICustomAttributeProvider, mscorlib],[System.Type, mscorlib]][]
00007ffddbcc8610 8348 298313756 System.Char[]
00007ffddbcc74c0 1799807 489361500 System.String
000000000022e250 312151 855949918 Free
00007ffddbccc768 109156 1135674368 System.Byte[]
有了这种先入为主的思想,我想你肯定发现了托管堆上的这个 50256 的 System.Net.Sockets.SocketAsyncEventArgs
,看样子这回泄漏和 Socket 脱不了干系了,接下来可以查下这些 SocketAsyncEventArgs
到底被谁引用着?
4. 查看 SocketAsyncEventArgs 引用根
要想查看引用根,先从 SocketAsyncEventArgs 中导几个 address 出来。
||2:2:080> !dumpheap -mt 00007ffddac72958 0 0000000001000000
Address MT Size
0000000000cc9dc0 00007ffddac72958 456
0000000000ccc0d8 00007ffddac72958 456
0000000000ccc358 00007ffddac72958 456
0000000000cce670 00007ffddac72958 456
0000000000cce8f0 00007ffddac72958 456
0000000000cd0c08 00007ffddac72958 456
0000000000cd0e88 00007ffddac72958 456
0000000000cd31a0 00007ffddac72958 456
0000000000cd3420 00007ffddac72958 456
0000000000cd5738 00007ffddac72958 456
0000000000cd59b8 00007ffddac72958 456
0000000000cd7cd0 00007ffddac72958 456
然后查看第一个和第二个address的引用根。
||2:2:080> !gcroot 0000000000cc9dc0
Thread 86e4:
0000000018ecec20 00007ffd7dff06b4 xxxHttpServer.DaemonThread`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].DaemonThreadStart()
rbp+10: 0000000018ececb0
-> 000000000102e8c8 xxxHttpServer.DaemonThread`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]]
-> 00000000010313a8 xxxHttpServer.xxxHttpRequestServer`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]]
-> 000000000105b330 xxxHttpServer.HttpSocketTokenPool`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]]
-> 000000000105b348 System.Collections.Generic.Stack`1[[xxxHttpServer.HttpSocketToken`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]], xxxHttpServer]]
-> 0000000010d36178 xxxHttpServer.HttpSocketToken`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]][]
-> 0000000008c93588 xxxHttpServer.HttpSocketToken`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]]
-> 0000000000cc9dc0 System.Net.Sockets.SocketAsyncEventArgs
||2:2:080> !gcroot 0000000000ccc0d8
Thread 86e4:
0000000018ecec20 00007ffd7dff06b4 xxxHttpServer.DaemonThread`2[[System.__Canon, mscorlib],[System.__Canon, mscorlib]].DaemonThreadStart()
rbp+10: 0000000018ececb0
-> 000000000102e8c8 xxxHttpServer.DaemonThread`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]]
-> 00000000010313a8 xxxHttpServer.xxxHttpRequestServer`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]]
-> 000000000105b330 xxxHttpServer.HttpSocketTokenPool`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]]
-> 000000000105b348 System.Collections.Generic.Stack`1[[xxxHttpServer.HttpSocketToken`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]], xxxHttpServer]]
-> 0000000010d36178 xxxHttpServer.HttpSocketToken`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]][]
-> 0000000000ccc080 xxxHttpServer.HttpSocketToken`2[[xxx.xxx, xxx],[xxx.RequestInfo, xxx]]
-> 0000000000ccc0d8 System.Net.Sockets.SocketAsyncEventArgs
从输出信息看,貌似程序自己搭了一个 HttpServer,还搞了一个 HttpSocketTokenPool 池,好奇心来了,把这个类导出来看看怎么写的?
5. 寻找问题代码
还是老办法,使用 !savemodule
导出问题代码,然后使用 ILSpy 进行反编译。
说实话,这个 pool 封装的挺简陋的,既然 SocketAsyncEventArgs 有 5W+,我猜测这个 m_pool
池中估计也得好几万,为了验证思路,可以用 windbg 把它挖出来。
从图中的size可以看出,这个 pool 有大概 2.5w 的 HttpSocket,这就说明这个所谓的 Socket Pool
其实并没有封装好。
三:总结
想自己封装一个Pool,得要实现一些复杂的逻辑,而不能仅仅是一个 PUSH 和 POP 就完事了。。。 所以优化方向也很明确,想办法控制住这个 Pool,实现 Pool 该实现的效果。
更多高质量干货:参见我的 GitHub: dotnetfly
相关推荐
- 无力吐槽的自动续费(你被自动续费困扰过吗?)
-
今天因为工作需要,需要在百度文库上下载一篇文章。没办法,确实需要也有必要,只能老老实实的按要求买了个VIP。过去在百度文库上有过类似经历,当时为了写论文买了一个月的VIP,后面也没有太注意,直到第二个...
- 百度文库推出“文源计划”创作者可一键认领文档
-
11月7日,百度文库发布了旨在保护创作者权益的“文源计划”。所谓“文源计划”,即为每一篇文档找到源头,让创作者享受更多的权益。据百度文库总经理李小婉介绍,文源计划分为三部分,分别是版权认证、版权扶持和...
- 有开放大学学号的同学,百度文库高校版可以用了。
-
还在网上找百度文库的下载方式,只要从身边的朋友在读开放大学的,那他(她)的学号就可以登陆到国家开放大学图书馆,还使用百度文库高校版来下载。与百度文库稍有不同,但足够使用了。现转国图链接如下:htt...
- 搜索资源方法推荐(搜索资源的方法)
-
今天msgbox就要教大家如何又快又准的搜到各类资源,第一点,排除干扰百度搜索出来啊经常前排展示它的产品以及百度文库,如何去除呢?很简单,后面输入空格减号百度文库,比如你搜高等数学百度文库很多,只要后...
- 一行代码搞定百度文库VIP功能(2021百度文库vip账号密码共享)
-
百度文库作为大家常用查资料找文档的平台,大多数文档我们都可以直接在百度文库找到,然而百度文库也有让人头痛的时候。好不容易找到一篇合适的文档,当你准备复制的时候他却提示你需要开通VIP才能复制~~~下载...
- 百度文库文档批量上传工具用户说明书
-
百度文库文档批量上传工具用户说明书1、软件主要功能1、批量上传文档到百度文库,支持上传到收费、VIP专享、优享以及共享。2、支持自动分类和自动获取标签3、支持多用户切换,一个账户传满可以切换到...
- 百度文库现在都看不到文档是否上传成功,要凉了吗?
-
打开知识店铺,百度文库文档里显示都是下载这一按键,上传的文档也看不到是否成功?咋情况,要取消了吗?没通过审核的也不让你删除,是几个意思,想通吃吗?现在百度上传文档也很费劲,有时弄了半天的资料上传审核过...
- 微信推广引流108式:利用百度文库长期分享软文引流
-
百度文库相对于百度知道、百度百科来说,操作上没那么多条条框框,规则上也相对好把握些。做一条百度知道所花费的精力一般都会比做一条百度文库的要多些,老马个人操作下来觉得百度文库更好把握。但见仁见智吧,今天...
- 职场“避雷”指南 百度文库推出标准化劳动合同范本
-
轰轰烈烈的毕业季结束了,众多应届生在经过了“职场海选”后,已正式成为职场生力军的一员。这一阶段,除了熟悉业务,签订劳动合同、了解职场福利也迅速被提上日程。而随着国人法律意识的增强,百度文库内《劳动合同...
- 《百度文库》:素材精选宝库(百度文库官网首页)
-
《百度文库》:独特功能助力选择高质量素材在当今信息爆炸的时代,如何高效地获取并利用有价值的素材成为了许多人面临的挑战。而《百度文库》作为百度公司推出的一款在线文档分享平台,凭借其丰富的资源、强大的功能...
- 深度整合和开放AI能力 百度文库和网盘推出内容操作系统「沧舟OS」
-
【TechWeb】4月25日消息,Create2025百度AI开发者大会上,百度文库和百度网盘推出全球首个内容操作系统——沧舟OS。基于沧舟OS,百度文库APP全新上线「GenFlow超能搭子」...
- 女子发现大二作业被百度文库要求付费下载,律师:平台侵权,应赔偿
-
近日,28岁的黎女士在百度百科搜索家乡的小地名时,发现了自己在大二完成的课题作业。她继续搜索,发现多个平台收录了该文,比如豆丁网和文档之家等,有的还设置了付费或积分下载。2月15日,九派新闻记者以用户...
- 2016杀入百度文库的新捷径,只有少数人才知道的喔
-
百度的产品在SEO优化中的分量真不用多说,其实很多人都像我一样一直在找捷径。但是我经常发现很多人都是在用死方法。比如发贴吧发帖而不知道去申请一个吧主,知道自问自答而不知道去申请一个合作资格。口碑和贴吧...
- 百度文库付费文档搜索方法(百度文库付费文档搜索方法有哪些)
-
一直以来,百度文库中无论是个人中心还是个人主页,都没有像淘宝一样的店内搜索功能,连最近新开的知识店铺也没有设计店内搜索功能,这无论是对上传用户还是下载用户都不方便,上传用户想要搜索自己的文档无法办到...
- 供读者免费使用!泰达图书馆机构版百度文库新年上新啦
-
在泰达图书馆读者使用百度文库数字资源不需要VIP,免-费-用!惊不惊喜?快来了解一下吧……新年伊始,为满足区域企业、高校、科研院所以及居民群众在教学、科研及学习过程中,对各类文献资源的需求,泰达图书馆...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 如何绘制折线图 (52)
- javaabstract (48)
- 新浪微博头像 (53)
- grub4dos (66)
- s扫描器 (51)
- httpfile dll (48)
- ps实例教程 (55)
- taskmgr (51)
- s spline (61)
- vnc远程控制 (47)
- 数据丢失 (47)
- wbem (57)
- flac文件 (72)
- 网页制作基础教程 (53)
- 镜像文件刻录 (61)
- ug5 0软件免费下载 (78)
- debian下载 (53)
- ubuntu10 04 (60)
- web qq登录 (59)
- 笔记本变成无线路由 (52)
- flash player 11 4 (50)
- 右键菜单清理 (78)
- cuteftp 注册码 (57)
- ospf协议 (53)
- ms17 010 下载 (60)