对 .NET FileSystemWatcher引发内存碎片化的 反思

[复制链接]
发表于 2025-11-21 18:38:04 | 显示全部楼层 |阅读模式
一:配景

1. 讲故事

前些天又碰到了一例 FileSystemWatcher 引发的内存碎片化故障,但这个碎片化不是由于经典的 reloadOnChange=true 导致的,以是我以为有须要做一次深度的反思,供以后碰到雷同标题提供技能上的办理方法,这篇我们就来体系的讲授下 两种碎片化方式的观察方法。
二:经典的 FileSystemWatcher 碎片化

1. 测试代码

这种碎片化是由 reloadOnChange=true 引发的,祸根紧张是步伐员将 .netframework 读取设置文件的方式套在了 .net 上,为了方便演示,先上一段测试代码。
  1.     internal class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             for (int i = 0; i < 100000; i++)
  6.             {
  7.                 IConfiguration configuration = BuildConfiguration();
  8.                 string appName = configuration["AppName"];
  9.                 Console.WriteLine($"i={i} 应用名称: {appName}");
  10.             }
  11.             Console.ReadLine();
  12.         }
  13.         static IConfiguration BuildConfiguration()
  14.         {
  15.             return new ConfigurationBuilder()
  16.                 .SetBasePath(Directory.GetCurrentDirectory())
  17.                 .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  18.                 .Build();
  19.         }
  20.     }
复制代码
卦中的代码非常简单,就是每次读取 AppName 时都调了一下 BuildConfiguration 方法,仅此而已,但将步伐跑起来之后,居然发现步伐吃了 2.2G 的内存,真是没边的事,截图如下:

为了找出缘故起因,上 windbg 附加,利用 !dumpheap -stat 观察托管堆,截图如下:

从卦中可以看到两点信息:

  • Free 独占 1.39G,这是经典的内存碎片化。
  • FileSystemWatcher 高达 1290 个,表明步伐存在大量的文件监控监控。
看到上面两点信息,肯定要有条件反射,是不是 reloadOnChange: true 导致的。
2. 是 reloadOnChange 导致的吗

要想找到答案,可以深挖 Microsoft.Extensions.Configuration.ConfigurationRoot 类,即代码 BuildConfiguration(); 的返回范例,为了方便可视化观察,我用 vs 直接找下给各人看看,截图如下:

有了这个脉络,就可以利用 windbg 下钻观察,终极就找到了 k__BackingField = 1 的铁证,参考如下:
  1. 0:008> !dumpobj /d 17dd2f41fa0
  2. Name:        Microsoft.Extensions.Configuration.ConfigurationRoot
  3. MethodTable: 00007ff9d8707a48
  4. EEClass:     00007ff9d86e97b0
  5. Tracked Type: false
  6. Size:        40(0x28) bytes
  7. File:        D:\travels\src\Example\Example_0_1\bin\Debug\net8.0\Microsoft.Extensions.Configuration.dll
  8. Fields:
  9.               MT    Field   Offset                 Type VT     Attr            Value Name
  10. 00007ff9d8706c48  4000016        8 ...on.Abstractions]]  0 instance 0000017dd2f3e520 _providers
  11. 00007ff9d880ba28  4000017       10 ...Private.CoreLib]]  0 instance 0000017dd2f42018 _changeTokenRegistrations
  12. 00007ff9d8708940  4000018       18 ...rationReloadToken  0 instance 0000017dd2f41fc8 _changeToken
  13. 0:008> !DumpObj /d 0000017dd2f3e520
  14. Name:        System.Collections.Generic.List`1[[Microsoft.Extensions.Configuration.IConfigurationProvider, Microsoft.Extensions.Configuration.Abstractions]]
  15. MethodTable: 00007ff9d87069d0
  16. EEClass:     00007ff9d86a10f8
  17. Tracked Type: false
  18. Size:        32(0x20) bytes
  19. File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.22\System.Private.CoreLib.dll
  20. Fields:
  21.               MT    Field   Offset                 Type VT     Attr            Value Name
  22. 00007ff9d891d1a0  400226e        8     System.__Canon[]  0 instance 0000017dd2f41f68 _items
  23. 00007ff9d8551188  400226f       10         System.Int32  1 instance                1 _size
  24. 00007ff9d8551188  4002270       14         System.Int32  1 instance                1 _version
  25. 00007ff9d891d1a0  4002271        8     System.__Canon[]  0   static dynamic statics NYI                 s_emptyArray
  26. 0:008> !DumpArray /d 0000017dd2f41f68
  27. Name:        Microsoft.Extensions.Configuration.IConfigurationProvider[]
  28. MethodTable: 00007ff9d8707cf0
  29. EEClass:     00007ff9d851c440
  30. Size:        56(0x38) bytes
  31. Array:       Rank 1, Number of elements 4, Type CLASS
  32. Element Methodtable: 00007ff9d8706938
  33. [0] 0000017dd2f3e540
  34. [1] null
  35. [2] null
  36. [3] null
  37. 0:008> !DumpObj /d 0000017dd2f3e540
  38. Name:        Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider
  39. MethodTable: 00007ff9d8708200
  40. EEClass:     00007ff9d86e9ab8
  41. Tracked Type: false
  42. Size:        48(0x30) bytes
  43. File:        D:\travels\src\Example\Example_0_1\bin\Debug\net8.0\Microsoft.Extensions.Configuration.Json.dll
  44. Fields:
  45.               MT    Field   Offset                 Type VT     Attr            Value Name
  46. 00007ff9d8708940  4000012        8 ...rationReloadToken  0 instance 0000017dd2f437e0 _reloadToken
  47. 00007ff9d8708cf0  4000013       10 ...Private.CoreLib]]  0 instance 0000017dd2f42298 <Data>k__BackingField
  48. 00007ff9d8662820  4000005       18   System.IDisposable  0 instance 0000017dd2f3e690 _changeTokenRegistration
  49. 00007ff9d8701b98  4000006       20 ...nfigurationSource  0 instance 0000017dd2f3e4b8 <Source>k__BackingField
  50. 0:008> !DumpObj /d 0000017dd2f3e4b8
  51. Name:        Microsoft.Extensions.Configuration.Json.JsonConfigurationSource
  52. MethodTable: 00007ff9d8701c88
  53. EEClass:     00007ff9d86e7868
  54. Tracked Type: false
  55. Size:        48(0x30) bytes
  56. File:        D:\travels\src\Example\Example_0_1\bin\Debug\net8.0\Microsoft.Extensions.Configuration.Json.dll
  57. Fields:
  58.               MT    Field   Offset                 Type VT     Attr            Value Name
  59. 00007ff9d86d8188  4000007        8 ...ers.IFileProvider  0 instance 0000017dd2f3e230 <FileProvider>k__BackingField
  60. 00007ff9d85cec08  4000008       10        System.String  0 instance 0000017d00100510 <Path>k__BackingField
  61. 00007ff9d851d070  4000009       24       System.Boolean  1 instance                0 <Optional>k__BackingField
  62. 00007ff9d851d070  400000a       25       System.Boolean  1 instance                1 <ReloadOnChange>k__BackingField
  63. 00007ff9d8551188  400000b       20         System.Int32  1 instance              250 <ReloadDelay>k__BackingField
  64. 00007ff9d8708420  400000c       18 ....FileExtensions]]  0 instance 0000000000000000 <OnLoadException>k__BackingField
复制代码
三:非经典的 FileSystemWatcher 碎片化

1. 测试代码

有的时间会出现 FileSystemWatcher 很少,但 overlapped 许多的环境,这种环境很大概率不是 reloadOnChange: true 导致的,截图如下:

像这种环境大概就须要开启追踪了,可以借助🐂👃的harmony 搞定,那怎样做呢?可以钩住 FileSystemWatcher 的全部构造函数,通过记录调用栈来观察到底是什么代码调用的,从而探求祸根,参考代码如下:
  1.     internal class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             var harmony = new Harmony("com.example.fswatcher");
  6.             harmony.PatchAll();
  7.             for (int i = 0; i < 5; i++)
  8.             {
  9.                 IConfiguration configuration = BuildConfiguration();
  10.                 string appName = configuration["AppName"];
  11.                 Console.WriteLine($"i={i} 应用名称: {appName}");
  12.             }
  13.             Console.ReadLine();
  14.         }
  15.         static IConfiguration BuildConfiguration()
  16.         {
  17.             return new ConfigurationBuilder()
  18.                 .SetBasePath(Directory.GetCurrentDirectory())
  19.                 .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  20.                 .Build();
  21.         }
  22.     }
  23.     [HarmonyPatch]
  24.     public class FileSystemWatcherConstructorsPatch
  25.     {
  26.         [HarmonyTargetMethod]
  27.         static IEnumerable<MethodBase> TargetMethods()
  28.         {
  29.             // 一次性获取所有公共实例构造函数
  30.             return typeof(FileSystemWatcher).GetConstructors(BindingFlags.Public | BindingFlags.Instance);
  31.         }
  32.         [HarmonyPostfix]
  33.         public static void Postfix(FileSystemWatcher __instance)
  34.         {
  35.             Console.WriteLine($"[Harmony] FileSystemWatcher 构造函数被调用");
  36.             Console.WriteLine($"[Harmony] 路径: '{__instance.Path ?? "null"}', 过滤器: '{__instance.Filter ?? "null"}'");
  37.             Console.WriteLine($"[Harmony] 调用栈:");
  38.             Console.WriteLine(Environment.StackTrace);
  39.         }
  40.     }
复制代码

从卦中可以看到,原来这个 FileSystemWatcher 是我们的用户代码 BuildConfiguration 搞的哈,这就极大的缩小的困绕圈,从而快速定位祸根。
四:总结

许多的内存碎片化每每都能看到 FileSystemWatcher 的身影,盼望这篇的反思和总结能给各人带来资助。

本帖子中包含更多资源

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

×
回复

使用道具 举报

登录后关闭弹窗

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