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

作者:用户722786812344日期:2025/11/21

1. 讲故事

前些天又遇到了一例 FileSystemWatcher 引发的内存碎片化故障,但这个碎片化不是因为经典的 reloadOnChange=true 导致的,所以我觉得有必要做一次深度的反思,供以后遇到类似问题提供技术上的解决方法,这篇我们就来系统的讲解下 两种碎片化方式的调查方法。

二:经典的 FileSystemWatcher 碎片化

1. 测试代码

这种碎片化是由 reloadOnChange=true 引发的,祸根主要是程序员将 .netframework 读取配置文件的方式套在了 .net 上,为了方便演示,先上一段测试代码。

1
2    internal class Program
3    {
4        static void Main(string[] args)
5        {
6            for (int i = 0; i < 100000; i++)
7            {
8                IConfiguration configuration = BuildConfiguration();
9                string appName = configuration["AppName"];
10                Console.WriteLine($"i={i} 应用名称: {appName}");
11            }
12
13            Console.ReadLine();
14        }
15
16        static IConfiguration BuildConfiguration()
17        {
18            return new ConfigurationBuilder()
19                .SetBasePath(Directory.GetCurrentDirectory())
20                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
21                .Build();
22        }
23    }
24

卦中的代码非常简单,就是每次读取 AppName 时都调了一下 BuildConfiguration 方法,仅此而已,但将程序跑起来之后,居然发现程序吃了 2.2G 的内存,真是没边的事,截图如下:

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

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

  1. Free 独占 1.39G,这是经典的内存碎片化。
  2. FileSystemWatcher 高达 1290 个,表明程序存在大量的文件监控。

看到上面两点信息,一定要有条件反射,是不是 reloadOnChange: true 导致的。

2. 是 reloadOnChange 导致的吗

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

有了这个脉络,就可以使用 windbg 下钻观察,最终就找到了 <ReloadOnChange>k__BackingField = 1 的铁证,参考如下:

1
20:008> !dumpobj /d 17dd2f41fa0
3Name:        Microsoft.Extensions.Configuration.ConfigurationRoot
4MethodTable: 00007ff9d8707a48
5EEClass:     00007ff9d86e97b0
6Tracked Type: false
7Size:        40(0x28) bytes
8File:        D:\travels\src\Example\Example_0_1\bin\Debug\net8.0\Microsoft.Extensions.Configuration.dll
9Fields:
10              MT    Field   Offset                 Type VT     Attr            Value Name
1100007ff9d8706c48  4000016        8 ...on.Abstractions]]  0 instance 0000017dd2f3e520 _providers
1200007ff9d880ba28  4000017       10 ...Private.CoreLib]]  0 instance 0000017dd2f42018 _changeTokenRegistrations
1300007ff9d8708940  4000018       18 ...rationReloadToken  0 instance 0000017dd2f41fc8 _changeToken
140:008> !DumpObj /d 0000017dd2f3e520
15Name:        System.Collections.Generic.List`1[[Microsoft.Extensions.Configuration.IConfigurationProvider, Microsoft.Extensions.Configuration.Abstractions]]
16MethodTable: 00007ff9d87069d0
17EEClass:     00007ff9d86a10f8
18Tracked Type: false
19Size:        32(0x20) bytes
20File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.22\System.Private.CoreLib.dll
21Fields:
22              MT    Field   Offset                 Type VT     Attr            Value Name
2300007ff9d891d1a0  400226e        8     System.__Canon[]  0 instance 0000017dd2f41f68 _items
2400007ff9d8551188  400226f       10         System.Int32  1 instance                1 _size
2500007ff9d8551188  4002270       14         System.Int32  1 instance                1 _version
2600007ff9d891d1a0  4002271        8     System.__Canon[]  0   static dynamic statics NYI                 s_emptyArray
270:008> !DumpArray /d 0000017dd2f41f68
28Name:        Microsoft.Extensions.Configuration.IConfigurationProvider[]
29MethodTable: 00007ff9d8707cf0
30EEClass:     00007ff9d851c440
31Size:        56(0x38) bytes
32Array:       Rank 1, Number of elements 4, Type CLASS
33Element Methodtable: 00007ff9d8706938
34[0] 0000017dd2f3e540
35[1] null
36[2] null
37[3] null
380:008> !DumpObj /d 0000017dd2f3e540
39Name:        Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider
40MethodTable: 00007ff9d8708200
41EEClass:     00007ff9d86e9ab8
42Tracked Type: false
43Size:        48(0x30) bytes
44File:        D:\travels\src\Example\Example_0_1\bin\Debug\net8.0\Microsoft.Extensions.Configuration.Json.dll
45Fields:
46              MT    Field   Offset                 Type VT     Attr            Value Name
4700007ff9d8708940  4000012        8 ...rationReloadToken  0 instance 0000017dd2f437e0 _reloadToken
4800007ff9d8708cf0  4000013       10 ...Private.CoreLib]]  0 instance 0000017dd2f42298 <Data>k__BackingF https://www.jiwenlaw.com/ield
4900007ff9d8662820  4000005       18   System.IDisposable  0 instance 0000017dd2f3e690 _changeTokenRegistration
5000007ff9d8701b98  4000006       20 ...nfigurationSource  0 instance 0000017dd2f3e4b8 <Source>k__BackingField
510:008> !DumpObj /d 0000017dd2f3e4b8
52Name:        Microsoft.Extensions.Configuration.Json.JsonConfigurationSource
53MethodTable: 00007ff9d8701c88
54EEClass:     00007ff9d86e7868
55Tracked Type: false
56Size:        48(0x30) bytes
57File:        D:\travels\src\Example\Example_0_1\bin\Debug\net8.0\Microsoft.Extensions.Configuration.Json.dll
58Fields:
59              MT    Field   Offset                 Type VT     Attr            Value Name
6000007ff9d86d8188  4000007        8 ...ers.IFileProvider  0 instance 0000017dd2f3e230 <FileProvider>k__BackingField
6100007ff9d85cec08  4000008       10        System.String  0 instance 0000017d00100510 <Path>k__BackingField
6200007ff9d851d070  4000009       24       System.Boolean  1 instance                0 <Optional>k__BackingField
6300007ff9d851d070  400000a       25       System.Boolean  1 instance                1 <ReloadOnChange>k__BackingField
6400007ff9d8551188  400000b       20         System.Int32  1 instance              250 <ReloadDelay>k__BackingField
6500007ff9d8708420  400000c       18 ....FileExtensions]]  0 instance 0000000000000000 <OnLoadException>k__BackingField
66

三:非经典的 FileSystemWatcher 碎片化

1. 测试代码

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

像这种情况可能就需要开启追踪了,可以借助🐂👃的harmony 搞定,那如何做呢?可以钩住 FileSystemWatcher 的所有构造函数,通过记录调用栈来观察到底是什么代码调用的,从而寻找祸根,参考代码如下:

1
2    internal class Program
3    {
4        static void Main(string[] args)
5        {
6            var harmony = new Harmony("com.example.fswatcher");
7            harmony.PatchAll();
8
9            for (int i = 0; i < 5; i++)
10            {
11                IConfiguration configuration = BuildConfiguration();
12                string appName = configuration["AppName"];
13                Console.WriteLine($"i={i} 应用名称: {appName}");
14            }
15
16            Console.ReadLine(https://www.jiwenlaw.com/ );
17        }
18
19        static IConfiguration BuildConfiguration()
20        {
21            return new ConfigurationBuilder()
22                .SetBasePath(Directory.GetCurrentDirectory())
23                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
24                .Build();
25        }
26    }
27
28    [HarmonyPatch]
29    public class FileSystemWatcherConstructorsPatch
30    {
31        [HarmonyTargetMethod]
32        static IEnumerable<MethodBase> TargetMethods()
33        {
34            // 一次性获取所有公共实例构造函数
35            return typeof(FileSystemWatcher).GetConstructors(BindingFlags.Public | BindingFlags.Instance);
36        }
37
38        [HarmonyPostfix]
39        public static void Postfix(FileSystemWatcher __instance)
40        {
41            Console.WriteLine($"[Harmony] FileSystemWatcher 构造函数被调用");
42            Console.WriteLine($"[Harmony] 路径: https://www.jiwenlaw.com/ '{__instance.Path ?? "null"}', 过滤器: '{__instance.Filter ?? "null"}'");
43            Console.WriteLine($"[Harmony] 调用栈:");
44            Console.WriteLine(Environment.StackTrace);
45        }
46    }
47

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

四:总结

很多的内存碎片化往往都能看到 FileSystemWatcher 的身影,希望这篇的反思和总结能给大家带来帮助。


对 .NET FileSystemWatcher引发内存碎片化的 反思》 是转载文章,点击查看原文


相关推荐


从 Flink 到 Doris 的实时数据写入实践——基于 Flink CDC 构建更实时高效的数据集成链路
SelectDB2025/11/19

Flink-Doris-Connector 作为 Apache Flink 与 Doris 之间的桥梁,打通了实时数据同步、维表关联与高效写入的关键链路。本文将深入解析 Flink-Doris-Connector 三大典型场景中的设计与实现,并结合 Flink CDC 详细介绍了整库同步的解决方案,助力构建更加高效、稳定的实时数据处理体系。 一、Apache Doris 简介 Apache Doris 是一款基于 MPP 架构的高性能、实时的分析型数据库,整体架构精简,只有 FE 、BE 两个系


Vue 3.0 源码解读
艾光远2025/11/18

1. 工程架构设计 Vue 3 是一个现代化的前端框架,采用模块化设计,源码项目被划分为多个模块,每个模块负责不同的功能。 1.1. compiler-core compiler-core 是 Vue 3 的编译核心模块,主要负责将模板转换为渲染函数。其模块如下: Parser(解析器):将模板字符串解析成抽象语法树(AST)。 Transform(转换器):遍历 AST,进行必要的转换,比如处理指令、插值、事件等。 Codegen(代码生成器):将转换后的 AST 转换成 Jav


用 Rust 构建 Git 提交历史可视化工具
掘金者阿豪2025/11/17

在软件开发中,版本控制系统的历史记录往往承载着项目的演进脉络。然而,当项目规模扩大、分支增多时,纯文本的 git log 输出很难直观地展现提交之间的复杂关系。今天,我想分享一个用 Rust 构建的轻量级工具 —— git-graph-rs,它能把 Git 仓库的提交历史转换为可视化的图结构,为代码审查、项目复盘和工程决策提供直观的支持。 @TOC 为什么需要可视化? 在参与大型项目时,我经常会遇到这样的场景: 需要快速了解某个功能分支的合并路径 在代码审查时想知道某个提交在整体历史中的位置


Python 的内置函数 oct
IMPYLH2025/11/16

Python 内建函数列表 > Python 的内置函数 oct Python 的内置函数 oct() 用于将一个整数转换为八进制(以 8 为基数)字符串表示形式。该函数返回的字符串以 0o 为前缀,表示这是一个八进制数。 语法 oct(x) 参数 x:必须是一个整数(可以是十进制、二进制、十六进制或其他形式的整数)。如果 x 不是整数,则需要先实现 __index__() 方法返回一个整数。 返回值 返回一个以 0o 为前缀的八进制字符串。 示例 十进制转八进制 prin


【大模型】重磅升级!文心 ERNIE-5.0 新一代原生全模态大模型,这你都不认可它吗?!
南方者2025/11/14

🧩 前言速读 11 月 13 日,2025百度世界大会上,新一代「原生全模态」大模型文心 5.0 正式亮相,2.4 万亿参数量,采用原生全模态统一建模技术,具备全模态理解与生成能力,支持「文本、图像、音频、视频」等多种信息的输入与输出,将国内大模型竞争力推向全球顶尖水准。 大会上强调:“智能本身是最大的应用,技术迭代速度是唯一的护城河”,而文心 5.0 正是这一理念的最新实践 —— 它不仅是参数规模的跃升,更标志着 AI 从 “单模态处理” 迈入 “原生多模态融合” 的新阶段。 接下来,让


OpenCVSharp:ArUco 标记检测与透视变换
mingupup2025/11/13

前言 对于.NET开发者而言,入门OpenCV的一个很舒适的方式就是先去使用OpenCVSharp,它是 OpenCV 的 .NET 封装,而且作者还开源了一个示例库,可以通过示例库进行入门学习。 OpenCVSharp仓库地址:github.com/shimat/open… opencvsharp_samples仓库地址:github.com/shimat/open… 作者提供了几十个可以直接运行的示例代码,一开始可以先大概运行一下这些示例,看一下用这个库可以实现哪些功能。 入门第一步就是


🚀 MateChat发布V1.10.0版本,支持附件上传及体验问题修复,欢迎体验~
2025/11/12

✨ 本期亮点 最新发布的 MateChat V1.10.0 版本新增文件列表组件和重新生成功能等特性,希望这个版本为你带来全新的体验! 🎯 核心功能升级(新特性) 🔄 新增文件列表组件 1、基本用法 McFileList 组件的核心功能是接收一个文件对象数组,并将它们渲染为信息卡片。通过 fileItems 属性传入数据,并可使用 context 属性控制其在不同场景下的外观,详情点击文件列表组件Demo 2、不同上下文与状态 McFileList 提供了两种上下文模式和多种文件状态,以适


Service Worker 深度解析:让你的 Web 应用离线也能飞
前端嘿起2025/11/10

在现代 Web 开发中,用户体验已经成为了衡量一个应用成功与否的重要标准。用户不仅希望网站加载速度快,还希望即使在网络不稳定或完全断网的情况下也能正常使用应用。这就引出了我们今天的主角——Service Worker。 前言 Service Worker 是一种在浏览器后台运行的脚本,它独立于网页主线程,可以拦截网络请求、缓存资源,甚至在离线状态下也能提供完整的用户体验。它是实现 PWA(渐进式 Web 应用)的核心技术之一,为 Web 应用带来了原生应用般的离线能力。 在本文中,我们将从基础


Thread.sleep 与 Task.sleep 终极对决:Swift 并发世界的 “魔法休眠术” 揭秘
大熊猫侯佩2025/11/8

📜 引子:霍格沃茨的 “并发魔咒” 危机 在霍格沃茨城堡顶层的 “魔法程序与咒语实验室” 里,金色的阳光透过彩绘玻璃洒在悬浮的魔法屏幕上。哈利・波特正对着一段闪烁着蓝光的 Swift 代码抓耳挠腮,罗恩在一旁急得直戳魔杖 —— 他们负责的 “魁地奇赛事实时计分器” 又卡住了。 赫敏抱着厚厚的《Swift 并发魔法指南》凑过来,眉头紧锁:“肯定是上次加的‘休眠咒语’出了问题!我早就说过 Thread.sleep 像‘摄魂怪的拥抱’,会吸干线程的活力,你们偏不信!” 这时,实验室的门 “吱呀”


Godot游戏开发——C# (一)
云缘若仙2025/11/6

1. 素材管理 核心内容:明确游戏开发所需基础素材类型,为场景与节点提供资源支撑,具体包括: AssetBundle:资源打包容器,用于统一管理与加载资源; Audio 音频素材:提供游戏音效、背景音乐等音频资源; Sprites 精灵图片素材:提供角色、道具、场景元素等可视化图片资源。 2. 场景树与核心节点 节点类型 功能描述 Root Node(根节点) 场景树顶层节点,所有子节点均嵌套于其下,构成场景层级框架的基础。

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2025 聚合阅读