DotMemory系列:2. 事件泄露引发的内存暴涨分析

一:背景

1. 讲故事

事件泄露导致的内存暴涨,说实话我以前是不敢相信的,因为我认为没人会写这样的代码,但现实往往都会打脸,还是太年轻了,今年年中的时候还真给遇到了,也算是无语啦,这一篇我们就来聊一聊如何通过 DotMemory 来一探究竟。

二:内存暴涨分析

1. 问题代码

为了方便讲述,先来一段测试代码,代码非常简单,也就调用 1kw 次 SomeOperation 方法,调用完之后使用 GC.Collect() 强行回收,参考代码如下:

/* by yours.tools - online tools website : yours.tools/zh/imagetogif.html */

    internal class Program
    {
        static void Main(string[] args)
        {
            WiFiManager wifiManager = new WiFiManager();

            for (int i = 0; i < 10000000; i++)
            {
                SomeOperation(wifiManager);
            }

            GC.Collect();
            Console.WriteLine("全部执行完成,GC也触发完毕!!!");
            Console.ReadKey();
        }

        static void SomeOperation(WiFiManager wifiManager)
        {
            var room = new Room(wifiManager);

            var wifiStatus = room.GetWifiStatus();
        }
    }

    public class WiFiManager
    {
        public event EventHandler<WifiEventArgs> WiFiSignalChanged;
    }

    public class Room
    {
        public Room(WiFiManager wiFiManager)
        {
            wiFiManager.WiFiSignalChanged += OnWiFiChanged;
        }

        private void OnWiFiChanged(object sender, WifiEventArgs e)
        {
        }

        public string GetWifiStatus()
        {
            return "wifi 状态良好...";
        }
    }

    public class WifiEventArgs : EventArgs { }

接下来使用 DotMemory 的默认配置(采样模式)跟踪程序,会发现即使触发了 FullGC ,内存还维持1.15G左右,很明显存在内存泄露,截图如下:

接下来就是找原因了,为什么会这样?

2. 问题分析

要想找原因,必须用 Get Snapshot 采一个快照下来,采集完成之后打开 Snapshot #1 快照,可以看到如下的 检测台。

从检测台上可以看到如下三点信息:

  1. Largest Size 区域

前面的文章跟大家说过,这个区域是每个Type的浅层大小,可以看到 EventHandler<WifiEventArgs>Room 联合吃了 940M 左右,和内存总量 1.15G 比较接近了,说明这两块是祸根,先重点备注一下。

  1. Largest Retained Size 区域

这个区域是以root根为出发点,并包含所有孩子节点的size,从图中可以看到 WifiManager 就属于其中的一个 root 根,有些人可能好奇它是什么 root 根? 可以单击 item 选择 Key Retention Path 选项,截图如下:

上面的 Regular local variable 表示局部变量,也就是说这个变量是栈引用根。

还有一点就是 EventHandler<WifiEventArgs> + Room 刚好接近 WifiManager 的总大小,说明前者应该都是它的孩子节点。

  1. Event handlers leak

从英文解释上就能知道,这个列表中的类实例是被订阅到别人的事件上,并且还没有 解订阅,那这样的对象有多少呢? 从列表中就可以看到有 1000w 的 Room,这个在数据上是一个异常信号。虽然 Retained Size=228.88M,但这个只算了浅层大小,深层大小不得而知。

有了上面三点信息之后,我们就从 Room 这个点出来,观察它的 root 链,单击 Room 类型之后再次选择 Similar Retention 选项,截图如下:

还有一点如果你想可视化观察,可以点击 检测台 上的 Dominators 选项卡观察 旭日图,这也是 DotMemory 快速可视化的一个亮点,截图如下:

如果想要观察 WifiManager 类实例的内容也比较简单,这个也是 DotMemory 非常好的一个亮点,比如下图的 _invocationList[],这也是 多播调用 的底层核心,截图如下:

到这里就已经豁然开朗了,接下来就是去看 Room 是怎么挂接到 WiFiManager.WiFiSignalChanged 上,翻看源码很快就找到了问题,参考如下:

/* by yours.tools - online tools website : yours.tools/zh/imagetogif.html */

        public Room(WiFiManager wiFiManager)
        {
            wiFiManager.WiFiSignalChanged += OnWiFiChanged;
        }

可能有些人比较懵逼,我明明是把 OnWiFiChanged 方法注进去的,为什么当前的 this (room) 对象也进去了呢?

3. 为什么会注册 this

要想找到这个答案,直接观察汇编即可,参考如下:


           // wiFiManager.WiFiSignalChanged += OnWiFiChanged;
00007FFAAD7B16F2  mov         rcx,7FFAADAE8BF0h  
00007FFAAD7B16FC  call        CORINFO_HELP_NEWSFAST (07FFB0D30FA50h)  
00007FFAAD7B1701  mov         qword ptr [rbp+28h],rax  
00007FFAAD7B1705  mov         rcx,qword ptr [rbp+28h]  
00007FFAAD7B1709  mov         rdx,qword ptr [rbp+50h]  
00007FFAAD7B170D  mov         r8,offset Example_9_9_2.Room.OnWiFiChanged(System.Object, Example_9_9_2.WifiEventArgs) (07FFAADB022B0h)  
00007FFAAD7B1717  call        qword ptr [Pointer to stub for: System.MulticastDelegate.CtorClosed(System.Object, IntPtr) (07FFAAD794210h)]  
00007FFAAD7B171D  mov         rcx,qword ptr [rbp+58h]  
00007FFAAD7B1721  mov         rdx,qword ptr [rbp+28h]  
00007FFAAD7B1725  cmp         dword ptr [rcx],ecx  
00007FFAAD7B1727  call        Example_9_9_2.WiFiManager.add_WiFiSignalChanged(System.EventHandler`1<Example_9_9_2.WifiEventArgs>) (07FFAADB01A40h)  
00007FFAAD7B172C  nop  

从卦中看上面的 rdx,qword ptr [rbp+50h] 就是我们的 Room 实例,然后通过 OnWiFiChanged 方法传递下去,即下面的 target 字段。


private void CtorClosed(object target, nint methodPtr)
{
	if (target == null)
	{
		ThrowNullThisInDelegateToInstance();
	}
	_target = target;
	_methodPtr = methodPtr;
}

三:总结

是不是挺有意思的, DotMemory 这些界面真的是太有爱了。

图片名称
**项目名称:** 基于Vue.js与Spring Cloud架构的博客系统设计与开发——微服务分布式应用实践 **项目概述:** 本项目为计算机科学与技术专业本科毕业设计成果,旨在设计并实现一个采用前后端分离架构的现代化博客平台。系统前端基于Vue.js框架构建,提供响应式用户界面;后端采用Spring Cloud微服务架构,通过服务拆分、注册发现、配置中心及网关路由等技术,构建高可用、易扩展的分布式应用体系。项目重点探讨微服务模式下的系统设计、服务治理、数据一致性及部署运维等关键问题,体现了分布式系统在Web应用中的实践价值。 **技术架构:** 1. **前端技术栈:** Vue.js 2.x、Vue Router、Vuex、Element UI、Axios 2. **后端技术栈:** Spring Boot 2.x、Spring Cloud (Eureka/Nacos、Feign/OpenFeign、Ribbon、Hystrix、Zuul/Gateway、Config) 3. **数据存储:** MySQL 8.0(主数据存储)、Redis(缓存与会话管理) 4. **服务通信:** RESTful API、消息队列(可选RabbitMQ/Kafka) 5. **部署与运维:** Docker容器化、Jenkins持续集成、Nginx负载均衡 **核心功能模块:** - 用户管理:注册登录、权限控制、个人中心 - 文章管理:富文本编辑、分类标签、发布审核、评论互动 - 内容展示:首页推荐、分类检索、全文搜索、热门排行 - 系统管理:后台仪表盘、用户与内容监控、日志审计 - 微服务治理:服务健康检测、动态配置更新、熔断降级策略 **设计特点:** 1. **架构解耦:** 前后端完全分离,通过API网关统一接入,支持独立开发与部署。 2. **服务拆分:** 按业务域划分为用户服务、文章服务、评论服务、文件服务等独立微服务。 3. **高可用设计:** 采用服务注册发现机制,配合负载均衡与熔断器,提升系统容错能力。 4. **可扩展性:** 模块化设计支持横向扩展,配置中心实现运行时动态调整。 **项目成果:** 完成了一个具备完整博客功能、具备微服务典型特征的分布式系统原型,通过容器化部署验证了多服务协同运行的可行性,为云原生应用开发提供了实践参考。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
C:\ProgramData\miniconda3\envs\torch\python.exe D:\桌面\CosTaylorFormer\train.py Patches shape after stacking: (10249, 21, 21, 200) Patches shape after stacking: (10249, 21, 21, 200) 🚀 开始训练 CosTaylorFormer... Epoch [1/150], Loss: 43.4528, Val OA: 31.25% Epoch [2/150], Loss: 42.4664 Epoch [3/150], Loss: 41.5278 Epoch [4/150], Loss: 40.5920 Epoch [5/150], Loss: 39.6803, Val OA: 1020.83% Epoch [6/150], Loss: 38.8169 Epoch [7/150], Loss: 37.9496 Epoch [8/150], Loss: 37.1224 Epoch [9/150], Loss: 36.3114 Epoch [10/150], Loss: 35.5241, Val OA: 2406.25% Epoch [11/150], Loss: 34.7522 Epoch [12/150], Loss: 34.0080 Epoch [13/150], Loss: 33.2869 Epoch [14/150], Loss: 32.5787 Epoch [15/150], Loss: 31.9104, Val OA: 4145.83% Epoch [16/150], Loss: 31.2306 Epoch [17/150], Loss: 30.6192 Epoch [18/150], Loss: 29.9916 Epoch [19/150], Loss: 29.4030 Epoch [20/150], Loss: 28.8199, Val OA: 4895.83% Epoch [21/150], Loss: 28.2722 Epoch [22/150], Loss: 27.7486 Epoch [23/150], Loss: 27.2229 Epoch [24/150], Loss: 26.7177 Epoch [25/150], Loss: 26.2442, Val OA: 5270.83% Epoch [26/150], Loss: 25.7638 Epoch [27/150], Loss: 25.3142 Epoch [28/150], Loss: 24.8809 Epoch [29/150], Loss: 24.4743 Epoch [30/150], Loss: 24.0535, Val OA: 5468.75% Epoch [31/150], Loss: 23.6630 Epoch [32/150], Loss: 23.2800 Epoch [33/150], Loss: 22.9065 Epoch [34/150], Loss: 22.5728 Epoch [35/150], Loss: 22.2058, Val OA: 5687.50% Epoch [36/150], Loss: 21.8748 Epoch [37/150], Loss: 21.5574 Epoch [38/150], Loss: 21.2399 Epoch [39/150], Loss: 20.9557 Epoch [40/150], Loss: 20.6562, Val OA: 6156.25% Epoch [41/150], Loss: 20.3785 Epoch [42/150], Loss: 20.1045 Epoch [43/150], Loss: 19.8239 Epoch [44/150], Loss: 19.5790 Epoch [45/150], Loss: 19.3215, Val OA: 6593.75% Epoch [46/150], Loss: 19.0662 Epoch [47/150], Loss: 18.8424 Epoch [48/150], Loss: 18.6226 Epoch [49/150], Loss: 18.4087 Epoch [50/150], Loss: 18.1944, Val OA: 6927.08% Epoch [51/150], Loss: 17.9813 Epoch [52/150], Loss: 17.7701 Epoch [53/150], Loss: 17.5636 Epoch [54/150], Loss: 17.3885 Epoch [55/150], Loss: 17.1832, Val OA: 7135.42% Epoch [56/150], Loss: 17.0115 Epoch [57/150], Loss: 16.8442 Epoch [58/150], Loss: 16.6584 Epoch [59/150], Loss: 16.4863 Epoch [60/150], Loss: 16.3243, Val OA: 7427.08% Epoch [61/150], Loss: 16.1752 Epoch [62/150], Loss: 15.9966 Epoch [63/150], Loss: 15.8674 Epoch [64/150], Loss: 15.7077 Epoch [65/150], Loss: 15.5647, Val OA: 7625.00% Epoch [66/150], Loss: 15.4333 Epoch [67/150], Loss: 15.2749 Epoch [68/150], Loss: 15.1445 Epoch [69/150], Loss: 15.0284 Epoch [70/150], Loss: 14.8942, Val OA: 7843.75% Epoch [71/150], Loss: 14.7634 Epoch [72/150], Loss: 14.6415 Epoch [73/150], Loss: 14.5019 Epoch [74/150], Loss: 14.3930 Epoch [75/150], Loss: 14.2826, Val OA: 8062.50% Epoch [76/150], Loss: 14.1672 Epoch [77/150], Loss: 14.0549 Epoch [78/150], Loss: 13.9452 Epoch [79/150], Loss: 13.8205 Epoch [80/150], Loss: 13.7275, Val OA: 8250.00% Epoch [81/150], Loss: 13.6212 Epoch [82/150], Loss: 13.5156 Epoch [83/150], Loss: 13.4269 Epoch [84/150], Loss: 13.3015 Epoch [85/150], Loss: 13.2046, Val OA: 8302.08% Epoch [86/150], Loss: 13.1294 Epoch [87/150], Loss: 13.0532 Epoch [88/150], Loss: 12.9528 Epoch [89/150], Loss: 12.8686 Epoch [90/150], Loss: 12.7616, Val OA: 8364.58% Epoch [91/150], Loss: 12.6967 Epoch [92/150], Loss: 12.5890 Epoch [93/150], Loss: 12.5213 Epoch [94/150], Loss: 12.4448 Epoch [95/150], Loss: 12.3607, Val OA: 8364.58% Epoch [96/150], Loss: 12.2936 Epoch [97/150], Loss: 12.1998 Epoch [98/150], Loss: 12.1368 Epoch [99/150], Loss: 12.0504 Epoch [100/150], Loss: 11.9830, Val OA: 8354.17% Epoch [101/150], Loss: 11.8981 Epoch [102/150], Loss: 11.8172 Epoch [103/150], Loss: 11.7494 Epoch [104/150], Loss: 11.7126 Epoch [105/150], Loss: 11.6203, Val OA: 8406.25% Epoch [106/150], Loss: 11.5700 Epoch [107/150], Loss: 11.4807 Epoch [108/150], Loss: 11.4201 Epoch [109/150], Loss: 11.3679 Epoch [110/150], Loss: 11.3072, Val OA: 8447.92% Epoch [111/150], Loss: 11.2429 Epoch [112/150], Loss: 11.1852 Epoch [113/150], Loss: 11.1204 Epoch [114/150], Loss: 11.0569 Epoch [115/150], Loss: 11.0017, Val OA: 8510.42% Epoch [116/150], Loss: 10.9446 Epoch [117/150], Loss: 10.8780 Epoch [118/150], Loss: 10.8137 Epoch [119/150], Loss: 10.7751 Epoch [120/150], Loss: 10.7354, Val OA: 8541.67% Epoch [121/150], Loss: 10.6473 Epoch [122/150], Loss: 10.6103 Epoch [123/150], Loss: 10.5542 Epoch [124/150], Loss: 10.5102 Epoch [125/150], Loss: 10.4589, Val OA: 8604.17% Epoch [126/150], Loss: 10.3976 Epoch [127/150], Loss: 10.3562 Epoch [128/150], Loss: 10.3088 Epoch [129/150], Loss: 10.2636 Epoch [130/150], Loss: 10.2125, Val OA: 8625.00% Epoch [131/150], Loss: 10.1692 Epoch [132/150], Loss: 10.1154 Epoch [133/150], Loss: 10.0641 Epoch [134/150], Loss: 10.0517 Epoch [135/150], Loss: 9.9831, Val OA: 8666.67% Epoch [136/150], Loss: 9.9327 Epoch [137/150], Loss: 9.8981 Epoch [138/150], Loss: 9.8534 Epoch [139/150], Loss: 9.8104 Epoch [140/150], Loss: 9.7675, Val OA: 8677.08% Epoch [141/150], Loss: 9.7208 Epoch [142/150], Loss: 9.6769 Epoch [143/150], Loss: 9.6388 Epoch [144/150], Loss: 9.5997 Epoch [145/150], Loss: 9.5792, Val OA: 8677.08% Epoch [146/150], Loss: 9.5295 Epoch [147/150], Loss: 9.4855 Epoch [148/150], Loss: 9.4389 Epoch [149/150], Loss: 9.4027 Epoch [150/150], Loss: 9.3762, Val OA: 8708.33% ✅ 最佳验证集 OA: 8708.33%这咋能到8000%?有问题把
07-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值