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

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
一:配景

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.
复制代码

根据卦中的图和输出,终于找到了原来是 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
复制代码

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


修改后的代码如下,果然加了许多的业务逻辑来处置处罚。
  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天后给我反馈了好消息,盛意情溢于言表!

三:总结

dump之旅是一个补缀工不停自我修炼的过程,必须学会在绝望中探求盼望的本领。

免责声明:如果侵犯了您的权益,请联系站长及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金.

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表