找回密码
 立即注册
首页 业界区 业界 记一次 .NET 某低代码开发框架 内存暴涨分析 ...

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

乱蚣 昨天 12:05
一:背景

1. 讲故事

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

1. 为什么会内存暴涨

先还是老套路,用 !address -summary 观察下内存分布情况,输出如下:
  1. 0:000> !address -summary
  2. --- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
  3. Free                                    363     7dfd`e87c7000 ( 125.992 TB)           98.43%
  4. <unknown>                              9276      201`e5858000 (   2.007 TB)  99.96%    1.57%
  5. Heap                                     65        0`2547f000 ( 596.496 MB)   0.03%    0.00%
  6. Image                                  1855        0`09d35000 ( 157.207 MB)   0.01%    0.00%
  7. Stack                                    93        0`02c00000 (  44.000 MB)   0.00%    0.00%
  8. Other                                     9        0`001de000 (   1.867 MB)   0.00%    0.00%
  9. TEB                                      31        0`0003e000 ( 248.000 kB)   0.00%    0.00%
  10. PEB                                       1        0`00001000 (   4.000 kB)   0.00%    0.00%
  11. --- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
  12. MEM_FREE                                363     7dfd`e87c7000 ( 125.992 TB)           98.43%
  13. MEM_RESERVE                             690      201`2b6d4000 (   2.005 TB)  99.82%    1.57%
  14. MEM_COMMIT                            10640        0`ec155000 (   3.689 GB)   0.18%    0.00%
复制代码
从卦中可以看到,总计 3.6G 的总提交内存,看样子都落到了 Unk 区域,最好是托管层吃掉了,否则就麻烦了,接下来使用 !dumpheap -stat 观察,输出如下:
  1. 0:000> !dumpheap -stat
  2. Statistics:
  3.           MT      Count     TotalSize Class Name
  4. ...
  5. 0179c7715cb0  1,847,901   451,265,880 Free
  6. 7ffc6e0a2888          2   536,870,960 System.WeakReference<Microsoft.Extensions.DependencyInjection.ServiceProvider>[]
  7. 7ffc6e0a2260 60,873,978 1,460,975,472 System.WeakReference<Microsoft.Extensions.DependencyInjection.ServiceProvider>
  8. Total 63,333,893 objects, 2,494,520,292 bytes
复制代码
从卦中可以看到程序中有 6087w 个弱引用,接下来使用 !dumpheap -mt 7ffc6e0a2260 观察下列表详情,然后用 !gcroot 观察其引用根,参考如下:
  1. 0:000> !dumpheap -mt 7ffc6e0a2260
  2.          Address               MT           Size
  3.     017988001000     7ffc6e0a2260             24
  4.     017988001018     7ffc6e0a2260             24
  5.     017988001030     7ffc6e0a2260             24
  6.     017988001048     7ffc6e0a2260             24
  7.     017988001060     7ffc6e0a2260             24
  8.     017988001078     7ffc6e0a2260             24
  9.     017988001090     7ffc6e0a2260             24
  10.     0179880010a8     7ffc6e0a2260             24
  11.     ...
  12.     017a405f1020     7ffc6e0a2260             24
  13. 0:000> !gcroot   0179880010a8  
  14. Caching GC roots, this may take a while.
  15. Subsequent runs of this command will be faster.
复制代码
等了20多分钟都没有出来结果,可能 6kw 的根纵横交错让windbg不堪重负,没有就没撤了,使用内存搜索法寻找上级所属对象。这里就选择 017a405f1020 对象来开刀。
  1. 0:000> !dumpobj /d 17a405f1020
  2. Name:        System.WeakReference`1[[Microsoft.Extensions.DependencyInjection.ServiceProvider, Microsoft.Extensions.DependencyInjection]][]
  3. MethodTable: 00007ffc6e0a2888
  4. EEClass:     00007ffc6dbeb4f8
  5. Tracked Type: false
  6. Size:        536870936(0x20000018) bytes
  7. Array:       Rank 1, Number of elements 67108864, Type CLASS (Print Array)
  8. Fields:
  9. None
  10. 0:000> s-q 0 L?0xffffffffffffffff 17a405f1020
  11. 00000179`c95861d0  0000017a`405f1020 03a0dcfa`03a0dcfa
  12. 0:000> !lno 0000017a`405f1020
  13. Before:       017a405f1000 32 (0x20)                        Free
  14. Current:      017a405f1020 24 (0x18)                        System.WeakReference<Microsoft.Extensions.DependencyInjection.ServiceProvider>[]
  15. Error Detected: Object 17a405f1020 has a bad member at offset 12054c00: ??? [verify heap]
  16. Could not find object after 17a405f1020
  17. Heap local consistency not confirmed.
  18. 0:000> !lno 00000179`c95861d0
  19. Before:       0179c95861c8 32 (0x20)                        System.Collections.Generic.List<System.WeakReference<Microsoft.Extensions.DependencyInjection.ServiceProvider>>
  20. Next:         0179c95861e8 24 (0x18)                        System.WeakReference<Microsoft.Extensions.DependencyInjection.ServiceProvider>[]
  21. Heap local consistency confirmed.
  22. 0:000> !dumpobj /d 179c95861c8
  23. Name:        System.Collections.Generic.List`1[[System.WeakReference`1[[Microsoft.Extensions.DependencyInjection.ServiceProvider, Microsoft.Extensions.DependencyInjection]], System.Private.CoreLib]]
  24. MethodTable: 00007ffc6e0a2340
  25. EEClass:     00007ffc6dce0000
  26. Tracked Type: false
  27. Size:        32(0x20) bytes
  28. File:        D:\xxx\A_api\System.Private.CoreLib.dll
  29. Fields:
  30.               MT    Field   Offset                 Type VT     Attr            Value Name
  31. 00007ffc6de328f0  400209f        8     System.__Canon[]  0 instance 0000017a405f1020 _items
  32. 00007ffc6dc894b0  40020a0       10         System.Int32  1 instance         60873978 _size
  33. 00007ffc6dc894b0  40020a1       14         System.Int32  1 instance         60873978 _version
  34. 00007ffc6de328f0  40020a2        8     System.__Canon[]  0   static dynamic statics NYI                 s_emptyArray
  35. 0:000> s-q 0 L?0xffffffffffffffff 179c95861c8
  36. 00000179`c77571d8  00000179`c95861c8 00000000`00000000
  37. 00000179`c95861b8  00000179`c95861c8 0800004e`00000000
  38. 0:000> !lno 00000179`c77571d8
  39. Failed to find the segment of the managed heap where the object 179c77571d8 resides
  40. 0:000> !lno 00000179`c95861b8
  41. Before:       0179c9586108 192 (0xc0)                       Microsoft.Extensions.DependencyInjection.DependencyInjectionEventSource
  42. Next:         0179c95861c8 32 (0x20)                        System.Collections.Generic.List<System.WeakReference<Microsoft.Extensions.DependencyInjection.ServiceProvider>>
  43. Heap local consistency confirmed.
复制代码
1.png

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

从名字上看和 ETW 事件有关,接下来用 !eeversion 观察 .net 版本,寻找其对应的C#源代码。
  1. 0:000> !eeversion
  2. 6.0.3624.51421 free
  3. 6,0,3624,51421 @Commit: f1dd57165bfd91875761329ac3a8b17f6606ad18
  4. Workstation mode
  5. SOS Version: 9.0.13.2701 retail build
复制代码
2.png

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

4.png

修改后的代码如下,果然加了很多的业务逻辑来处理。
  1.         [NonEvent]
  2.         public void ServiceProviderBuilt(ServiceProvider provider)
  3.         {
  4.             lock (_providers)
  5.             {
  6.                 int providersCount = _providers.Count;
  7.                 if (providersCount > 0 &&
  8.                     (_survivingProvidersCount is int spc ? (uint)providersCount >= 2 * (uint)spc : providersCount == _providers.Capacity))
  9.                 {
  10.                     _providers.RemoveAll(static p => !p.TryGetTarget(out _));
  11.                     _survivingProvidersCount = _providers.Count;
  12.                 }
  13.                 _providers.Add(new WeakReference<ServiceProvider>(provider));
  14.             }
  15.             WriteServiceProviderBuilt(provider);
  16.         }
复制代码
从官方描述来看,就是有人创建了 scope,但后续没有调用 dispose 方法来及时释放,导致框架中的 WeakReference 引用滞留,引发内存暴涨,可以说两者都有责任吧。
解决办法很简单,两种方式:

  • 检查代码里写 BuildServiceProvider 的地方没有即时的 Dispose。
  • 升级到 .NET10 ,这是最简单粗暴的方法。
把结论告诉朋友后,朋友终于在2天后给我反馈了好消息,好心情溢于言表!
5.png

三:总结

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

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册