DC娱乐网

记一次 .NET 某低代码开发框架 内存暴涨分析

一:背景1. 讲故事微信里有一位朋友找到我,说他们公司的程序存在内存暴涨问题,自己分析了下没有找到原因,让我看下怎么回事
一:背景1. 讲故事

微信里有一位朋友找到我,说他们公司的程序存在内存暴涨问题,自己分析了下没有找到原因,让我看下怎么回事?由于大家都有dump分析基础,所以交流互通上还是很顺利的,接下来就是上dump分析啦。

二:内存暴涨分析1. 为什么会内存暴涨

先还是老套路,用 !address -summary观察下内存分布情况,输出如下:

0:000> !address -summary--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotalFree 363 7dfd`e87c7000 ( 125.992 TB) 98.43%9276 201`e5858000 ( 2.007 TB) 99.96% 1.57%Heap 65 0`2547f000 ( 596.496 MB) 0.03% 0.00%Image 1855 0`09d35000 ( 157.207 MB) 0.01% 0.00%Stack 93 0`02c00000 ( 44.000 MB) 0.00% 0.00%Other 9 0`001de000 ( 1.867 MB) 0.00% 0.00%TEB 31 0`0003e000 ( 248.000 kB) 0.00% 0.00%PEB 1 0`00001000 ( 4.000 kB) 0.00% 0.00%--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotalMEM_FREE 363 7dfd`e87c7000 ( 125.992 TB) 98.43%MEM_RESERVE 690 201`2b6d4000 ( 2.005 TB) 99.82% 1.57%MEM_COMMIT 10640 0`ec155000 ( 3.689 GB) 0.18% 0.00%

从卦中可以看到,总计 3.6G的总提交内存,看样子都落到了Unk区域,最好是托管层吃掉了,否则就麻烦了,接下来使用!dumpheap -stat观察,输出如下:

0:000> !dumpheap -statStatistics:MT Count TotalSize Class Name...0179c7715cb0 1,847,901 451,265,880 Free7ffc6e0a2888 2 536,870,960 System.WeakReference7ffc6e0a2260 60,873,978 1,460,975,472 System.WeakReferenceTotal 63,333,893 objects, 2,494,520,292 bytes

从卦中可以看到程序中有 6087w个弱引用,接下来使用!dumpheap -mt 7ffc6e0a2260观察下列表详情,然后用!gcroot观察其引用根,参考如下:

0:000> !dumpheap -mt 7ffc6e0a2260Address MT Size017988001000 7ffc6e0a2260 24017988001018 7ffc6e0a2260 24017988001030 7ffc6e0a2260 24017988001048 7ffc6e0a2260 24017988001060 7ffc6e0a2260 24017988001078 7ffc6e0a2260 24017988001090 7ffc6e0a2260 240179880010a8 7ffc6e0a2260 24...017a405f1020 7ffc6e0a2260 240:000> !gcroot 0179880010a8Caching GC roots, this may take a while.Subsequent runs of this command will be faster.

等了20多分钟都没有出来结果,可能 6kw 的根纵横交错让windbg不堪重负,没有就没撤了,使用内存搜索法寻找上级所属对象。这里就选择 017a405f1020对象来开刀。

0:000> !dumpobj /d 17a405f1020Name: System.WeakReference`1[[Microsoft.Extensions.DependencyInjection.ServiceProvider, Microsoft.Extensions.DependencyInjection]]MethodTable: 00007ffc6e0a2888EEClass: 00007ffc6dbeb4f8Tracked Type: falseSize: 536870936(0x20000018) bytesArray: Rank 1, Number of elements 67108864, Type CLASS (Print Array)Fields:None0:000> s-q 0 L?0xffffffffffffffff 17a405f102000000179`c95861d0 0000017a`405f1020 03a0dcfa`03a0dcfa0:000> !lno 0000017a`405f1020Before: 017a405f1000 32 (0x20) FreeCurrent: 017a405f1020 24 (0x18) System.WeakReferenceError Detected: Object 17a405f1020 has a bad member at offset 12054c00: ??? [verify heap]Could not find object after 17a405f1020Heap local consistency not confirmed.0:000> !lno 00000179`c95861d0Before: 0179c95861c8 32 (0x20) System.Collections.Generic.ListNext: 0179c95861e8 24 (0x18) System.WeakReferenceHeap local consistency confirmed.0:000> !dumpobj /d 179c95861c8Name: System.Collections.Generic.List`1[[System.WeakReference`1[[Microsoft.Extensions.DependencyInjection.ServiceProvider, Microsoft.Extensions.DependencyInjection]], System.Private.CoreLib]]MethodTable: 00007ffc6e0a2340EEClass: 00007ffc6dce0000Tracked Type: falseSize: 32(0x20) bytesFile: D:\xxx\A_api\System.Private.CoreLib.dllFields:MT Field Offset Type VT Attr Value Name00007ffc6de328f0 400209f 8 System.__Canon 0 instance 0000017a405f1020 _items00007ffc6dc894b0 40020a0 10 System.Int32 1 instance 60873978 _size00007ffc6dc894b0 40020a1 14 System.Int32 1 instance 60873978 _version00007ffc6de328f0 40020a2 8 System.__Canon 0 staticdynamic statics NYI s_emptyArray0:000> s-q 0 L?0xffffffffffffffff 179c95861c800000179`c77571d8 00000179`c95861c8 00000000`0000000000000179`c95861b8 00000179`c95861c8 0800004e`000000000:000> !lno 00000179`c77571d8Failed to find the segment of the managed heap where the object 179c77571d8 resides0:000> !lno 00000179`c95861b8Before: 0179c9586108 192 (0xc0) Microsoft.Extensions.DependencyInjection.DependencyInjectionEventSourceNext: 0179c95861c8 32 (0x20) System.Collections.Generic.ListHeap local consistency confirmed.

根据卦中的图和输出,终于找到了原来是 DependencyInjectionEventSource._providers承担了所有,接下来的关注点就来到了DependencyInjectionEventSource。

2. xxxEventSource 是什么

从名字上看和 ETW 事件有关,接下来用 !eeversion观察 .net 版本,寻找其对应的C。

0:000> !eeversion6.0.3624.51421 free6,0,3624,51421 @Commit: f1dd57165bfd91875761329ac3a8b17f6606ad18Workstation modeSOS Version: 9.0.13.2701 retail build

从上面的源代码看,其实也看不出来个所以,毕竟底层的架构我不熟悉,本着我不是第一个吃螃蟹的人,所以拿关键词在网上索一下,果然 stephentoub 大佬在去年4月份就发现了这个问题,在 .net10 中做了修复,看描述是一个优化级的bug,官方链接:https://github.com/dotnet/runtime/issues/114599 截图如下:

修改后的代码如下,果然加了很多的业务逻辑来处理。

[NonEvent]public void ServiceProviderBuilt(ServiceProvider provider){lock (_providers){int providersCount = _providers.Count;if (providersCount > 0 &&(_survivingProvidersCount isint spc ? (uint)providersCount >= 2 * (uint)spc : providersCount == _providers.Capacity)){_providers.RemoveAll(static p => !p.TryGetTarget(out _));_survivingProvidersCount = _providers.Count;}_providers.Add(new WeakReference}WriteServiceProviderBuilt(provider);}

从官方描述来看,就是有人创建了 scope,但后续没有调用 dispose 方法来及时释放,导致框架中的 WeakReference 引用滞留,引发内存暴涨,可以说两者都有责任吧。

解决办法很简单,两种方式:

检查代码里写 BuildServiceProvider 的地方没有即时的 Dispose。

升级到 .NET10 ,这是最简单粗暴的方法。

把结论告诉朋友后,朋友终于在2天后给我反馈了好消息,好心情溢于言表!

三:总结

dump之旅是一个修理工不断自我修炼的过程,必须学会在绝望中寻找希望的能力。