记一次 .NET 某电商定向爬虫 内存碎片化分析
cac55 2024-10-11 10:52 26 浏览 0 评论
一:背景
1. 讲故事
上个月有位朋友wx找到我,说他的程序存在内存泄漏问题,寻求如何解决? 如下图所示:
从截图中可以看出,这位朋友对 windbg 的操作还是有些熟悉的,可能缺乏一定的实操经验,所以用了几个命令之后就不知道怎么排查下去了。
既然找到我,那就以我的个人经验在他的dump上继续分析寻找罪魁祸首,闲话不多说,上windbg说话。
二:Windbg 分析
1. 真的存在内存泄漏吗?
追这个系列的朋友应该知道,我无数次的用 !address -summary 和 !eeheap -gc 这两个命令来判断当前的内存泄漏是属于托管层还是非托管层。
0:000> !address -summary
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Free 358 7dfc`67f60000 ( 125.986 TB) 98.43%
<unknown> 1087 203`88b6e000 ( 2.014 TB) 99.99% 1.57%
Image 1532 0`09f11000 ( 159.066 MB) 0.01% 0.00%
Heap 249 0`03453000 ( 52.324 MB) 0.00% 0.00%
Stack 66 0`01fc0000 ( 31.750 MB) 0.00% 0.00%
Other 10 0`001d1000 ( 1.816 MB) 0.00% 0.00%
TEB 22 0`0002c000 ( 176.000 kB) 0.00% 0.00%
PEB 1 0`00001000 ( 4.000 kB) 0.00% 0.00%
--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_MAPPED 183 200`00d06000 ( 2.000 TB) 99.30% 1.56%
MEM_PRIVATE 1252 3`8d479000 ( 14.207 GB) 0.69% 0.01%
MEM_IMAGE 1532 0`09f11000 ( 159.066 MB) 0.01% 0.00%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE 358 7dfc`67f60000 ( 125.986 TB) 98.43%
MEM_RESERVE 749 200`28a9b000 ( 2.001 TB) 99.33% 1.56%
MEM_COMMIT 2218 3`6f5f5000 ( 13.740 GB) 0.67% 0.01%
0:000> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00000026DA8DA928
generation 1 starts at 0x00000026DA7FC348
generation 2 starts at 0x00000024C4691000
ephemeral segment allocation context: none
segment begin allocated size
00000024C4690000 00000024C4691000 00000024D468FF28 0xfffef28(268431144)
00000024EECF0000 00000024EECF1000 00000024FECF0000 0xffff000(268431360)
000000248D6F0000 000000248D6F1000 000000249D6EFEF8 0xfffeef8(268431096)
...
00000026D66D0000 00000026D66D1000 00000026DBA3CA30 0x536ba30(87472688)
Large object heap starts at 0x00000024D4691000
segment begin allocated size
00000024D4690000 00000024D4691000 00000024DC67C318 0x7feb318(134132504)
00000024E60F0000 00000024E60F1000 00000024EE0637C8 0x7f727c8(133638088)
0000002482140000 0000002482141000 000000248A08F338 0x7f4e338(133489464)
00000024A6770000 00000024A6771000 00000024AE76F6C0 0x7ffe6c0(134211264)
...
000000278E6D0000 000000278E6D1000 000000279635F2D0 0x7c8e2d0(130605776)
00000029233E0000 00000029233E1000 000000292AF672F8 0x7b862f8(129524472)
000000292B3E0000 000000292B3E1000 0000002931A5ED60 0x667dd60(107470176)
000000299B3E0000 000000299B3E1000 00000029A20095B0 0x6c285b0(113411504)
000000281E6D0000 000000281E6D1000 0000002825CD3F58 0x7602f58(123744088)
00000028266D0000 00000028266D1000 000000282D5CAD50 0x6ef9d50(116366672)
000000282E6D0000 000000282E6D1000 0000002833CA0880 0x55cf880(89979008)
00000029A33E0000 00000029A33E1000 00000029A684D300 0x346c300(54969088)
Total Size: Size: 0x353f96d88 (14293757320) bytes.
------------------------------
GC Heap Size: Size: 0x353f96d88 (14293757320) bytes.
从输出看,当前进程占用 MEM_COMMIT=13.7G,托管堆内存占用 14293757320 = 13.3G,很明显这属于简单模式的 托管内存泄漏,根据经验,托管堆上可能有什么大对象,这里用 !dumpheap -stat 命令。
0:000> !dumpheap -stat
Statistics:
MT Count TotalSize Class Name
00007ff9ed6ea268 3956842 94964208 System.Collections.Generic.Dictionary`2+KeyCollection[[System.String, System.Private.CoreLib],[Serilog.Events.LogEventPropertyValue, Serilog]]
00007ff9ed5e6d28 3842435 166405016 Serilog.Parsing.MessageTemplateToken[]
00007ff9ed5e5e28 3842434 184436832 Serilog.Events.MessageTemplate
00007ff9ecccf090 4011012 203304420 System.Int32[]
00007ff9ed647078 3956849 253238336 Serilog.Events.LogEvent
00007ff9ed6e7b48 3956849 284893128 System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[Serilog.Events.LogEventPropertyValue, Serilog]]
00007ff9ed5e74e8 9259598 296307136 Serilog.Parsing.TextToken
00007ff9ed6471b0 12551808 301243392 Serilog.Events.ScalarValue
00007ff9ed6e8308 3956849 729078048 System.Collections.Generic.Dictionary`2+Entry[[System.String, System.Private.CoreLib],[Serilog.Events.LogEventPropertyValue, Serilog]][]
00007ff9eccb1e18 16546412 3987811940 System.String
00000024c3b8faf0 82904 7382993568 Free
我去,托管堆最大的对象居然是 Free,大概占用 7.3G, 这就了,不按套路出牌哈,为了更好的理解,先来简要解释下 free 对象。
2. 简单解释 free
其实简而言之,free对象是被gc标记成已回收的空闲块但并未提交给操作系统释放的对象,那怎么去证明呢? 可以先到托管堆上找一个free块。
0:000> !dumpheap -type Free
Address MT Size
00000024c4691000 00000024c3b8faf0 24 Free
00000024c46a2448 00000024c3b8faf0 24 Free
00000024c46b26d8 00000024c3b8faf0 40 Free
00000024c47e4418 00000024c3b8faf0 40 Free
00000024c4925680 00000024c3b8faf0 40 Free
00000024c49284a8 00000024c3b8faf0 64 Free
00000024c4947a90 00000024c3b8faf0 192 Free
00000024c4951f70 00000024c3b8faf0 48 Free
000000249d6ea3a8 00000024c3b8faf0 640 Free
- 用 !do 命令查看是否标记为free块
0:000> !do 000000249d6ea3a8
Free Object
Size: 640(0x280) bytes
- 用 dc 看一下这个 free 块的内容,是否有gc回收的痕迹。
0:000> dc 000000249d6ea3a8 000000249d6ea3a8+0x280
00000024`9d6ea3a8 c3b8faf0 00000024 00000268 00000000 ....$...h.......
00000024`9d6ea3b8 9d6ea6d0 00000024 00000000 00000000 ..n.$...........
00000024`9d6ea3c8 ed3ae2b8 00007ff9 9d6ea3a8 00000024 ..:.......n.$...
00000024`9d6ea3d8 00000000 00000000 edcc9118 00007ff9 ................
00000024`9d6ea3e8 00000000 00000000 00000000 00000000 ................
00000024`9d6ea3f8 00000000 00000000 00000000 00000000 ................
00000024`9d6ea408 eeb07a50 00007ff9 9d6ea3c8 00000024 Pz........n.$...
00000024`9d6ea418 00000000 00000000 ef292ee8 00007ff9 ..........).....
00000024`9d6ea428 9d6ea408 00000024 00000000 00000000 ..n.$...........
...
00000024`9d6ea4a8 eeb0a158 00007ff9 9d6ea420 00000024 X....... .n.$...
00000024`9d6ea4b8 00000000 00000000 00000000 00000000 ................
00000024`9d6ea4c8 ef293818 00007ff9 9d6ea4a8 00000024 .8).......n.$...
00000024`9d6ea4d8 00000000 00000000 ee8357e0 00007ff9 .........W......
...
00000024`9d6ea508 eed37b40 00007ff9 00000000 00000000 @{..............
00000024`9d6ea518 00000000 00000000 00000000 00000000 ................
00000024`9d6ea528 c4699b48 00000024 00000000 00000000 H.i.$...........
00000024`9d6ea538 00000000 07000440 00000001 00000000 ....@...........
00000024`9d6ea548 00000000 00000000 00000000 00000000 ................
00000024`9d6ea558 00000000 00000000 ef2af6e0 00007ff9 ..........*.....
00000024`9d6ea568 00000000 00000000 00000000 00000000 ................
00000024`9d6ea578 00000000 00000000 c4699b48 00000024 ........H.i.$...
00000024`9d6ea588 00000000 00000000 00000000 07000400 ................
...
00000024`9d6ea628 ef2afd08 ..*.
可以看到,这个free块生前是有一些残留的内容字符,好了,对 free 块有基本了解后,接下来我们继续探究下。
3. 到底是什么阻止了free块的合并?
按照正常逻辑,大多free块会在gc回收完之后合并内存时,那些被清空后的segment会被操作系统释放的,但这个dump并没有,也就说明这里有什么东西阻止了free块的合并,那到底是什么呢? 有经验的朋友会说,可以观察下 gc 的句柄表,命令为 !gchandles -stat 。
0:000> !gchandles -stat
Statistics:
MT Count TotalSize Class Name
...
00007ff9ed15c0f0 1008 72576 System.Reflection.Emit.DynamicResolver
00007ff9ecbf6618 38 409344 System.Object[]
Total 1784 objects
Handles:
Strong Handles: 233
Pinned Handles: 16
Async Pinned Handles: 18
Ref Count Handles: 1
Weak Long Handles: 1327
Weak Short Handles: 144
Dependent Handles: 45
从输出看,这里并没有什么可疑的地方,那怎么办呢? 实操经验多的话,这里还是有一些经验值得分享的,比如观察 free 在 heap 上的布局特征,往往就有重大发现。
4. 查看 free 块的布局特征
为了简化输出结果,我把范围限定到 heap 上某一个 segment 上,比如这里的: 00000029233E0000 000000292AF672F8,所以命令就是 !dumpheap 00000029233E0000 000000292AF672F8
0:000> !dumpheap 00000029233E0000 000000292AF672F8
Address MT Size
00000029233e1000 00000024c3b8faf0 8291896 Free
0000002923bc9638 00007ff9eccb1e18 108448
0000002923be3dd8 00000024c3b8faf0 29931248 Free
000000292586f4c8 00007ff9eccb1e18 301328
00000029258b8dd8 00000024c3b8faf0 41384784 Free
0000002928030928 00007ff9eccb1e18 301328
000000292807a238 00000024c3b8faf0 2542664 Free
00000029282e6e80 00007ff9eccb1e18 108448
0000002928301620 00000024c3b8faf0 29915032 Free
0000002929f88db8 00007ff9eccb1e18 301328
0000002929fd26c8 00000024c3b8faf0 2746688 Free
000000292a271008 00007ff9eccb1e18 291304
000000292a2b81f0 00000024c3b8faf0 1019600 Free
000000292a3b10c0 00007ff9eccb1e18 108448
000000292a3cb860 00000024c3b8faf0 10601048 Free
000000292ade7ab8 00007ff9eccb1e18 301328
000000292ae313c8 00000024c3b8faf0 280808 Free
000000292ae75cb0 00007ff9eccb1e18 280854
000000292aeba5c8 00000024c3b8faf0 416584 Free
000000292af20110 00007ff9eccb1e18 291304
Statistics:
MT Count TotalSize Class Name
00007ff9eccb1e18 10 2394118 System.String
00000024c3b8faf0 10 127130352 Free
Total 20 objects
真是一看吓一跳,free 和 object 呈交替状,这就是为什么free块不能被合并的真正原因,说实话这种教科书式的 内存碎片化 dump,真是可遇不可求,接下来就抽几个 free 之间的 object 对象,看看到底是被什么引用着导致gc回收不掉。
5. 寻找 object 的引用链
要想查看 object 到底被谁引用着,可以用 !gcroot 命令,这里我抽二个看看。
0:000> !gcroot 0000002923bc9638
Thread 1878:
00000024C39BE4B0 00007FFA4C0B3522 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2922]
rbp+10: 00000024c39be520
-> 00000024C48AD6E0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions+<RunAsync>d__4, Microsoft.Extensions.Hosting.Abstractions]]
-> 00000024C48AD200 Microsoft.Extensions.Hosting.Internal.Host
-> 00000024C48AC538 Microsoft.Extensions.Logging.Logger`1[[Microsoft.Extensions.Hosting.Internal.Host, Microsoft.Extensions.Hosting]]
-> 00000024C48AC658 Microsoft.Extensions.Logging.Logger
-> 00000024C48AC680 Microsoft.Extensions.Logging.LoggerInformation[]
-> 00000024C48AC6E0 Serilog.Extensions.Logging.SerilogLogger
-> 00000024C48AC808 Serilog.Core.Logger
-> 00000024C48AC760 Serilog.Core.Logger
-> 00000024C47AD468 Serilog.Core.Logger
-> 00000024C47ABF08 Serilog.Core.Sinks.SafeAggregateSink
-> 00000024C47ABF20 Serilog.Core.ILogEventSink[]
-> 00000024C479C548 Serilog.Sinks.Grafana.Loki.LokiSink
-> 00000024C479C778 Serilog.Sinks.Grafana.Loki.Infrastructure.BoundedQueue`1[[Serilog.Events.LogEvent, Serilog]]
-> 00000024C479C7B8 System.Collections.Generic.Queue`1[[Serilog.Events.LogEvent, Serilog]]
-> 00000026E8C1A800 Serilog.Events.LogEvent[]
-> 00000026148D3308 Serilog.Events.LogEvent
-> 00000026148D4EF0 Serilog.Events.MessageTemplate
-> 0000002923BC9638 System.String
Found 1 unique roots (run '!gcroot -all' to see all roots).
0:000> !gcroot 000000292586f4c8
Thread 1878:
00000024C39BE4B0 00007FFA4C0B3522 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2922]
rbp+10: 00000024c39be520
-> 00000024C48AD6E0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions+<RunAsync>d__4, Microsoft.Extensions.Hosting.Abstractions]]
-> 00000024C48AD200 Microsoft.Extensions.Hosting.Internal.Host
-> 00000024C48AC538 Microsoft.Extensions.Logging.Logger`1[[Microsoft.Extensions.Hosting.Internal.Host, Microsoft.Extensions.Hosting]]
-> 00000024C48AC658 Microsoft.Extensions.Logging.Logger
-> 00000024C48AC680 Microsoft.Extensions.Logging.LoggerInformation[]
-> 00000024C48AC6E0 Serilog.Extensions.Logging.SerilogLogger
-> 00000024C48AC808 Serilog.Core.Logger
-> 00000024C48AC760 Serilog.Core.Logger
-> 00000024C47AD468 Serilog.Core.Logger
-> 00000024C47ABF08 Serilog.Core.Sinks.SafeAggregateSink
-> 00000024C47ABF20 Serilog.Core.ILogEventSink[]
-> 00000024C479C548 Serilog.Sinks.Grafana.Loki.LokiSink
-> 00000024C479C778 Serilog.Sinks.Grafana.Loki.Infrastructure.BoundedQueue`1[[Serilog.Events.LogEvent, Serilog]]
-> 00000024C479C7B8 System.Collections.Generic.Queue`1[[Serilog.Events.LogEvent, Serilog]]
-> 00000026E8C1A800 Serilog.Events.LogEvent[]
-> 0000002614BB7AC8 Serilog.Events.LogEvent
-> 0000002616D3CC40 Serilog.Events.MessageTemplate
-> 000000292586F4C8 System.String
Found 1 unique roots (run '!gcroot -all' to see all roots).
从引用链看,这些 object 都是通过 Serilog 日志组件发送给 Grafana.Loki ,通过对引用链对象的追踪,我发现 System.Collections.Generic.Queue 有重大问题,截图如下:
对,这个 queue 居然有 395w 的积压,到底积压了什么东西,可以用 !wdo 看一下string内容。
看样子这是一个失败重试,分析到这里思路大概就清晰了,就是 Grafana.Loki 或者 Serilog 组件出了什么问题,导致日志发送不到 Loki 中或者发送速度过慢,然后不断的积压所致,接下来把分析到的这些信息和朋友做了一个沟通,截图如下:
三:总结
本次内存碎片化的主要原因在于 Serilog 对接 Loki 的过程中产生的395w的queue积压所致,但我也只能分析到这里了,至于为什么有积压,这个还得朋友进一步调试分析,我相信这个问题很快就能得到解决
相关推荐
- 苹果新macOS、新Mac还没出,但已经有新版虚拟机软件Parallels Desktop 19
-
自从苹果电脑全面转向ARM架构芯片之后,想在新款Mac电脑上安装Windows或Linux系统,就只能依靠虚拟机软件了,其中ParallelsDesktop应该是比较多Mac用户选择使用的一款,现在...
- 这个开源神器可快速帮你安装 MacOS 虚拟机
-
大家好,我是JackTian。安装Windows和Linux操作系统是最熟悉不过的必备技能了。那么,给大家推荐一个非常实用的开源脚本:macos-guest-virtualbox.sh,帮你...
- 如何在VMware虚拟机上安装运行Mac OS系统??
-
想在自己的Windows电脑上安装一个MacOS体验一下苹果系统的小伙伴,教程来了!!!一、安装前准备虚拟机运行软件:VMwareWorkstationPro,版本:16.0.0。(可以注册)VM...
- 效率!MacOS下超级好用的Linux虚拟工具:Lima
-
对于MacOS用户来说,搭建Linux虚拟环境一直是件让人头疼的事。无论是VirtualBox还是商业的VMware,都显得过于笨重且配置复杂。今天,我们要介绍一个轻巧方便的纯命令行Linux虚拟工具...
- 普通电脑安装苹果MacOS+Windows10双系统,这次可不是虚拟机
-
上篇文章中说到,有一朋友因为工作需要,得临时使用苹果系统,笔者给他用VmwareWorkStation安装了一个苹果系统的虚拟机,结果装是装上了,但是发现调整分辨率有点小问题,文件传输也不方便。虽说...
- 官方证实苹果M1芯片不支持Windows 11
-
中关村在线消息:近日根据微软官方透露,目前已经确定Windows11不支持运行在苹果M1芯片上,这意味着过往在Mac电脑上安装Windows系统的做法在M1芯片的Mac电脑上并不适用。不过此前有网友...
- 这可能是 Mac 共享文件最详细的教程了
-
如果希望让一台Mac访问另一台Mac上的文件,就可以使用Mac的文件共享功能。而且不仅是Mac之间,甚至用iPhone、iPad、WindowsPC都可以访问Mac的共享文件...
- 在 M1/M2 Mac 上,让 Windows 11 免费“跑”起来
-
自从苹果在产品中逐步使用自研的M系列芯片淘汰掉英特尔芯片之后,很多事情都发生了改变。作者|KirkMcElhearn和JoshuaLong译者|弯月出品|CSDN(ID:CS...
- VMware Workstation克隆虚拟机后修改ip地址和mac地址
-
VMwareWorkstation克隆虚拟机,登录之后发现,克隆虚拟机不仅用户名相同,连ip地址、mac地址也是相同的,很显然访问相同ip地址的虚拟机是会出现ip地址冲突的。一、修改IP地址这就需要...
- VirtualBox7中安装macOS big sur,在windows10&11上「保姆级教程」
-
macOSBigSur是苹果公司研发的桌面端操作系统,于北京时间2020年6月23日在2020苹果全球开发者大会上发布。BigSur采用全新的精美设计,为主要app如Safari浏览器...
- 最强mac虚拟机Parallels Desktop 16 有哪些重要的新增功能?
-
ParallelsDesktop16正式发布,软件带来了一些显着的新功能和性能增强,包括对macOSBigSur的全面支持。当苹果推出macOSBigSur时,它终止了对Par...
- 关于在MacOS安装虚拟机的全过程(macos 安装虚拟机)
-
哈喽大家好,我是咕噜美乐蒂,很高兴又见面啦!下面美乐蒂将详细地给大家介绍一下在macOS上使用VMwareFusion创建虚拟机并安装操作系统的步骤:一、确认虚拟化支持:首先,确认你的Ma...
- macOS上也能轻松运行Win系统的虚拟机,你还不知道吗?
-
在macOS系统上运行Win系统的方式,虚拟机篇吉安光头强原创你是否曾经为了在Mac上运行Windows系统而烦恼不用着急,下面我将分享一种简单易行的方法,让你轻松在Mac上运行Windows系统准备...
- Mac M芯片上安装统信UOS 1070arm64虚拟机
-
原文链接:MacM芯片上安装统信UOS1070arm64虚拟机Hello,大家好啊!今天给大家带来一篇关于如何在苹果M系列芯片的Mac电脑上,通过VMware安装ARM64版统信UOS1070...
- 虚拟机不好用?Mac mini 多配一台Windows电脑,用远程桌面更好!
-
最近新入手了MacminiM4款,这里来更新一下相关问题,对于还没有购买Macmini,但是又想要用苹果电脑的朋友,一些参考,我觉得还是挺有用的!Macmini选择哪个渠道购买好?现在比较划算...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 苹果新macOS、新Mac还没出,但已经有新版虚拟机软件Parallels Desktop 19
- 这个开源神器可快速帮你安装 MacOS 虚拟机
- 如何在VMware虚拟机上安装运行Mac OS系统??
- 效率!MacOS下超级好用的Linux虚拟工具:Lima
- 普通电脑安装苹果MacOS+Windows10双系统,这次可不是虚拟机
- 官方证实苹果M1芯片不支持Windows 11
- 这可能是 Mac 共享文件最详细的教程了
- 在 M1/M2 Mac 上,让 Windows 11 免费“跑”起来
- VMware Workstation克隆虚拟机后修改ip地址和mac地址
- VirtualBox7中安装macOS big sur,在windows10&11上「保姆级教程」
- 标签列表
-
- 如何绘制折线图 (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)