程序内存一直在泄漏,原来是异步死循环了 !

一:背景

1. 讲故事

上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下:

1f21c76dbf710a00f743088a185b7da7.png

朋友这段话已经说的非常言简意赅了,那就上 windbg 说话吧。

二:Windbg 分析

1. 到底是哪一方面的泄漏

根据朋友描述,程序运行一段时间后,内存就炸了,应该没造成人员伤亡,不然也不会跟我wx聊天了,这里可以用 .time 看看当前的 process 跑了多久。

0:000> .time
Debug session time: Thu Oct 21 14:54:39.000 2021 (UTC + 8:00)
System Uptime: 6 days 4:37:27.851
Process Uptime: 0 days 0:40:14.000Kernel time: 0 days 0:01:55.000User time: 0 days 0:07:33.000

看的出来,这个 dump 是在程序跑了 40min 之后抓的,接下来我们比较一下 process 的内存和 gc堆 占比, 看看到底是哪一块的泄漏。

0:000> !address -summary--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_FREE                                327     7dfc`c665a000 ( 125.987 TB)           98.43%
MEM_RESERVE                             481      201`e91a2000 (   2.007 TB)  99.74%    1.57%
MEM_COMMIT                             2307        1`507f4000 (   5.258 GB)   0.26%    0.00%0:000> !eeheap -gc
Number of GC Heaps: 2
------------------------------GC Allocated Heap Size:    Size: 0x139923528 (5260850472) bytes.
GC Committed Heap Size:    Size: 0x13bf23000 (5300695040) bytes.

从卦中可轻松得知, 这是一个完完全全的托管堆内存泄漏。

2. 到底是什么占用了如此大的内存

知道是 托管层 的泄漏,感觉一下子就幸福起来了,接下来用 !dumpheap -stat 看看有没有什么大对象可挖。

0:000> !dumpheap -stat
Statistics:MT    Count    TotalSize Class Name
00007ffdeb1fc400  5362921    128710104 xxxBLLs.xxx.BundleBiz+<>c__DisplayClass20_0
00007ffdeaeff140  5362929    171613728 System.Collections.Generic.List`1[[xxx.xxx, xxx]]
00007ffdeaeff640  5362957    171615272 xxx.BLLs.Plan.Dto.xxx[]
00007ffde8171e18 16146362    841456072 System.String
00007ffdeb210098  5362921   1415811144 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]
00007ffdea9ca260  5362921   2359685240 xxx.Bundle

从输出看,内存主要被 xxx.BundleAsyncTaskMethodBuilder 两大类对象给吃掉了,数量都高达 536w,这里有一个非常有意思的地方,如果你了解异步,我相信你一看就能看出 AsyncTaskMethodBuilder + VoidTaskResult 是干嘛的,按照经验,这位朋友应该误入了 异步无限递归 ,那怎么去挖呢?接着往下看。

3. 寻找问题代码

看到上面的 xxx.BundleBiz+<DistributionBundle>d__20 了吗?这个正是异步操作所涉及到的类和方法,接下来用 ILSpy 反射出 BundleBiz 下的匿名类 <DistributionBundle>d__20 , 如下图所示:

526f411f4181e1b2616f710a96c2cd74.png

虽然找到了源码,但代码是 ILSpy 反编译出来的异步状态机,接下来的一个问题是,如何根据状态机代码反向寻找到 await ,async 代码呢?在 ILSpy 中有一个 used by 功能,在这里可以用起来了。

be987bda6b82bdaa6a3aaff5d78ebdac.png

双击 used by 就能看到真正的调用代码,简化后如下:

public async Task DistributionBundle(List<Bundle> list, List<xxx> bwdList, xxx item, List<xxx> sumDetails, List<xxx> details, BundleParameter bundleParameter, IEnumerable<dynamic> labels)
{int num = 0;foreach (xxx detail in sumDetails){IEnumerable<xxx> woDetails = details.Where((xxx w) => w.Size == detail.Size && w.Color == detail.Color);foreach (xxx item2 in woDetails){xxx}woDetails = woDetails.OrderBy((xxx s) => s.Seq).ToList();num++;xxxBundle bundle = new Bundle();Bundle bundle2 = bundle;bundle2.BundleId = await _repo.CreateBundleId();foreach (xxx item3 in woDetails){item3.TaskQty = item3.WoQty + Math.Ceiling(item3.WoQty * item3.OverCutRate);decimal value = default(decimal);}await DistributionBundle(list, bwdList, item, sumDetails, details, bundleParameter, labels);}
}

仔细看上面这段代码, 我去, await DistributionBundle(list, bwdList, item, sumDetails, details, bundleParameter, labels); 又调用了自身,看样子是某种条件下陷入了一个死递归。。。

有些朋友可能要问,除了经验之外,能从 dump 中分析出来吗?当然可以,从 500w+ 中抽一个看看它的 !gcroot 即可。

0:000> !DumpHeap /d -mt 00007ffdeb210098Address               MT     Size
000001a297913a68 00007ffdeb210098      264     
000001a297913b70 00007ffdeb210098      264  0:000> !gcroot 000001a297913a68
Thread 5ac:000000470B1EE4E0 00007FFE45103552 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2922]rbp+10: 000000470b1ee550->  000001A297A25D88 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]]->  000001A29796D8C0 Microsoft.Extensions.Hosting.Internal.Host...->  000001A298213248 System.Data.SqlClient.TdsParserStateObjectNative->  000001A32E6AB700 System.Threading.Tasks.TaskFactory`1+<>c__DisplayClass38_0`1[[System.Data.SqlClient.SqlDataReader, System.Data.SqlClient],[System.Data.CommandBehavior, System.Data.Common]]->  000001A32E6AB728 System.Threading.Tasks.Task`1[[System.Data.SqlClient.SqlDataReader, System.Data.SqlClient]]->  000001A32E6ABB18 System.Threading.Tasks.StandardTaskContinuation->  000001A32E6ABA80 System.Threading.Tasks.ContinuationTaskFromResultTask`1[[System.Data.SqlClient.SqlDataReader, System.Data.SqlClient]]->  000001A32E6AB6C0 System.Action`1[[System.Threading.Tasks.Task`1[[System.Data.SqlClient.SqlDataReader, System.Data.SqlClient]], System.Private.CoreLib]]->  000001A32E6AB428 System.Data.SqlClient.SqlCommand+<>c__DisplayClass130_0...->  000001A32E6ABC08 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.String, System.Private.CoreLib],[Dapper.SqlMapper+<QueryRowAsync>d__34`1[[System.String, System.Private.CoreLib]], Dapper]]->  000001A32E6ABD20 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.String, System.Private.CoreLib],[xxx.DALs.xxx.BundleRepo+<CreateBundleId>d__12, xxx]]->  000001A32E6ABD98 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]->  000001A32E6A6BD8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]->  000001A433250520 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]->  000001A32E69E0F8 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]->  000001A433247D28 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]->  000001A433246330 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]->  000001A32E69A568 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]->  000001A433245408 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[xxx.BundleBiz+<DistributionBundle>d__20, xxx]]...

从调用栈来看,代码貌似是从数据库读取记录的过程中陷入死循环的。

4. 为什么没有出现栈溢出

一看到无限循环,我相信很多朋友肯定要问,为啥没出现堆栈溢出,毕竟默认的线程栈空间仅仅 1M 而已,从 !gcroot 上看,这些引用都是挂在 5ac 线程上,也就是下面输出的 主线程 ,而且主线程栈也非常干净。

0:000> !t
ThreadCount:      30
UnstartedThread:  0
BackgroundThread: 24
PendingThread:    0
DeadThread:       5
Hosted Runtime:   noLock  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception0    1      5ac 000001A29752CDF0  202a020 Preemptive  0000000000000000:0000000000000000 000001a29754c570 0     MTA 4    2     1e64 000001A29752A490    2b220 Preemptive  0000000000000000:0000000000000000 000001a29754c570 0     MTA (Finalizer) 
...0:000> !clrstack 
OS Thread Id: 0x5ac (0)Child SP               IP Call Site
000000470B1EE1D0 00007ffe5eb30544 [GCFrame: 000000470b1ee1d0] 
000000470B1EE318 00007ffe5eb30544 [HelperMethodFrame_1OBJ: 000000470b1ee318] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)
000000470B1EE440 00007ffe45103c25 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken)
000000470B1EE4E0 00007ffe45103552 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2922]
000000470B1EE550 00007ffe451032cf System.Threading.Tasks.Task.InternalWaitCore(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2861]
000000470B1EE5D0 00007ffe45121b04 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task) [/_/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs @ 143]
000000470B1EE600 00007ffe4510482d System.Runtime.CompilerServices.TaskAwaiter.GetResult() [/_/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/TaskAwaiter.cs @ 106]
000000470B1EE630 00007ffe4de36595 Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(Microsoft.Extensions.Hosting.IHost) [/_/src/Hosting/Abstractions/src/HostingAbstractionsHostExtensions.cs @ 49]
000000470B1EE660 00007ffde80f3b4b xxx.Program.Main(System.String[])
000000470B1EE8B8 00007ffe47c06c93 [GCFrame: 000000470b1ee8b8] 
000000470B1EEE50 00007ffe47c06c93 [GCFrame: 000000470b1eee50]

如果你稍微了解一点异步的玩法,你应该知道这其中有一个 IO完成端口 的概念,它可以实现 句柄ThreadPool 的绑定,无限递归只不过是进了 IO完成端口 的待回调队列中而已,理论上和栈空间没什么关系,也就不会出现栈溢出了。

三:总结

本次内存泄漏的事故主要还是因为程序员的大意,也许是长期的 996 给弄恍惚了 😂😂😂,有了这些信息,修正起来相信会非常简单。

6c28baca1814474b1cab0348be0bd4dd.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294903.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

太巧了!学霸夫妻携手进入武大读博,两人的硕士导师也是一对夫妻

全世界只有3.14 % 的人关注了爆炸吧知识本文募格学术撰写。参考来源&#xff1a;湖北日报、双一流大学网、募格学术此前报道等科研人的爱情是什么样子&#xff1f;或许是在学术的路上他们相识相知&#xff0c;往后也将共同拼搏&#xff0c;在科研领域擦出更多火花~前段时间&…

github怎么隐藏自己的pr记录_记便签的软件哪个好?怎么及时记录自己的想法

我们在平时的工作和生活中&#xff0c;如果遇到一些需要及时记下来的东西时&#xff0c;很多人都会选用在便签中记录下来的方式。对于记便签的软件来说&#xff0c;不同的品牌有不同的特点&#xff0c;要想在众多便签软件中选择出一款适合自己的&#xff0c;就需要下点功夫了&a…

bkwin设置文本控件为多行模式

2019独角兽企业重金招聘Python工程师标准>>> 指定textmode&#xff0c; 给到文本控件的区域 <class name"xxxxx" textmode"40A011"/> textmode是DT_FROMT位或值 DrawText api对应的formt 转载于:https://my.oschina.net/u/2436679/bl…

性能测试组件CodeBenchmark V2发布

CodeBenchmark是一款可视化的性能测试组件&#xff0c;通过组件可以对一个或多个功能代码进行一个并发测试&#xff1b;最终通过详细的测试结果来对比不同代码的性能差异。组件的使用非常简单&#xff0c;构建一个控制台程序然后引入BeetleX.CodeBenchmark组件编写几个代码即可…

一所传闻要被“降级”的211高校,让这位网红教授“救活了”

全世界只有3.14 % 的人关注了爆炸吧知识本文来源&#xff1a;量子位&#xff08; ID: QbitAI&#xff09; 作者&#xff1a;金磊 发自 凹非寺太原理工大学&#xff0c;最近着实有点火。先是11月12日至13日&#xff0c;其官网一口气更新了3位「杰青」副校长&#xff0c;在高校任…

加载elementor时出现问题_不锈钢管在焊接时出现问题要怎么解决?

佛山不锈钢装饰管焊接时会出现各种问题&#xff0c;今天佛山不锈钢装饰管厂家喜有沃小编就简单的整理了一些常见问题及解决方法&#xff0c;希望能对大家有所帮助。佛山不锈钢装饰管焊接制作护栏1&#xff0c; 表面气孔佛山不锈钢装饰管在焊接时产生表面气孔的原因一般为使用了…

Android之玩转MPAndroidChart让(折线图、柱形图、饼状图、散列图、雷达图)优雅的舞动

第一步:不废话,先爆照 我的github地址:https://github.com/changechenyu/MPAndroidChartTest 第二步:介绍MPAndroidChart适用场景并把它的库文件导入我们开发的项目 介绍: MPAndroidChart是一款基于Android的开源图表库,MPAndroidChart不仅可以在Android设备上绘制各种…

在PowerDesigner中设计物理模型1——表和主外键

在PD中建立物理模型由以下几种办法&#xff1a; 直接新建物理模型。设计好概念模型&#xff0c;然后由概念模型生成物理模型。设计好逻辑模型&#xff0c;然后由逻辑模型生成物理模型。使用逆向工程的方法&#xff0c;连接到现有的数据库&#xff0c;由数据库生成物理模型。物理…

.NET 6新特性试用 | 无需配置开发人员异常页

前言在.NET 6之前&#xff0c;我们需要在“Startup.cs”文件中手工配置开发人员异常页&#xff1a;if (env.IsDevelopment()) {app.UseDeveloperExceptionPage();app.UseSwagger();app.UseSwaggerUI(c > c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web…

html5中表格如何等分,纯css3饼图五等分

先看效果图&#xff1a;HTML代码如下&#xff1a;pie良好优秀未提交需努力加油98%得分率css代码如下&#xff1a;.pinOfStudent{background-color: #ffffff;width: 100%;position: relative;}#tipZone{position:relative;left:0;right:0;top: 1em;width:12.5em;height:12.5em;m…

Android之开源框架NineOldAndroids动画库

1.介绍 Android3.0推出了全新的AnimationAPI&#xff0c;使用起来很方便&#xff0c;但是不能在3.0以下版本使用&#xff0c;NineOldAndroids是一个可以在任意Android版本上使用的AnimationAPI&#xff0c;API和Android3.0中的类似。 2.常用类 ObjectAnimator ValueAnimator A…

现在竟然还有补丝袜的?

1 看到柿子树千万不要踢&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 原来上床下桌还能这么简陋&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 你总是心太远心太远&#xff08;via.炕上的卡夫卡&#xff09;▼4 这个岗位的上一位同事离职原因▼5 …

微服务组件记事本:Skywalking的ES索引 · 收藏篇

&#xff08;2021年倒计时33天&#xff09;书接上文&#xff0c;在上回书中&#xff0c;我们说到了《微服务组件记事本&#xff1a;Skywalking执行效果 多图篇》&#xff0c;文章比较详细的展示了Skywalking中的各种数据和图表展示&#xff0c;有些小伙伴群里问我&#xff0c;…

计算机游戏50关,YELLOW游戏全50关攻略

yellow游戏是一款比较休闲的游戏作品&#xff0c;这款游戏中需要通过变换将屏幕全部调整成黄色&#xff0c;虽然操作不难&#xff0c;但比较考验思维能力&#xff0c;下面是全50关通关攻略&#xff0c;大家可以参考参考。【注&#xff1a;以下为攻略&#xff0c;不想看的不用点…

2021年将迎接你的是什么?

1 2021年迎接你的是...&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 原来帅真的可以当饭吃&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 我的芒果千层到了吗&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 这个岗位的上一…

log4j:WARN No appenders could be found for logger

2019独角兽企业重金招聘Python工程师标准>>> 提示这个错误是因为log4j的环境没有配置的原因&#xff0c;在src目录下新建一个log4j.properties的文件&#xff0c;黏贴如下内容即可解决。 # Configure logging for testing: optionally with log file log4j.rootLog…

推荐Mongodb GUI 可视化管理工具-NoSQLBooster

工欲善其事&#xff0c;必先利其器。虽然 MongoDB 已经提供了默认的管理工具&#xff08;CLI&#xff09;Shell 命令行&#xff0c;但是每次登录时都需要做身份认证&#xff0c;切换数据库等等一系列繁琐的操作&#xff0c;时间久了还是挺闹心的。实际开发中&#xff0c;还是会…

使用string.Format需要注意的一个性能问题

今天&#xff0c;我在写C#代码时&#xff0c;突然发现一个最熟悉的陌生人 —— string.Format。在写C#代码的日子里&#xff0c;与它朝夕相伴&#xff0c;却没有真正去了解它。只知道在字符串比较多时&#xff0c;用它比用加号进行字符串连接效率更高&#xff08;当然也更方便&…

为什么说,每个人都应该多读些书?

全世界只有3.14 % 的人关注了爆炸吧知识知乎上有个高赞问题&#xff1a;有哪些值得长期坚持下去就能改变人生的好习惯&#xff1f;其中最高频的回答是读书。随着经历和阅历的增加&#xff0c;越来越多的人清醒的认识到&#xff1a;读书不再是学生时代的事&#xff0c;而是一生的…

兄弟机cnc系统面板图解_FANUC软操作面板的应用介绍,真的太详细了

FANUC软操作面板介绍&#xff1a;FANUC软操作面板功能是CNC系统软件的一项功能&#xff0c;可以利用MDI键盘上的光标移动按键和轴移动方向按键 代替机床操作面板的按钮&#xff0c;结合显示器的显示&#xff0c;实现与操作面板同样的功能。在CNC系统安装到机床上之前进行调试试…