当前位置: 首页 > news >正文

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

一:背景

1. 讲故事

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

二:内存暴涨分析

1. 问题代码

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

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 上,翻看源码很快就找到了问题,参考如下:

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 这些界面真的是太有爱了。

图片名称
http://www.jsqmd.com/news/40836/

相关文章:

  • 2025 最新曝气器厂家口碑推荐榜:国际权威测评认证,平板 / 管式 / 微孔等全类型优质品牌汇总旋流 / 盘式微孔 / 振动曝气器公司推荐
  • 2025年比较好的石灰乳化泵厂家最新权威推荐排行榜
  • 2025年知名的百通电缆最新TOP品牌厂家排行
  • 2025 最新高温陶瓷纤维滤芯源头厂家权威推荐榜,国际协会测评认证聚焦高性能滤材核心企业
  • 2025年电磁加热器厂家权威推荐榜单:电磁蒸汽发生器/电磁烘干机/电磁采暖炉源头厂家精选
  • Rust 的错误处理:别拿类型系统当护身符 - 教程
  • 2025年靠谱的轻质抗爆墙品牌厂家排行榜
  • 2025年口碑好的化工厂抗爆墙最新TOP品牌厂家排行
  • 2025年城际专线网约车软件口碑排行榜
  • 2025年广州豪华大巴出租服务口碑推荐榜单
  • 2025年靠谱的青少年情绪管理成长训练平台哪家强
  • 2025年城际出行中巴包车公司排名
  • 2025年轧辊数控车床工厂哪家靠谱
  • golang: ubuntu 24.04安装go1.25.4
  • 2025年11月自吸泵厂家推荐榜单:预算导向选厂指南与top厂商实测对比
  • 22空间复用MIMO系统的MATLAB仿真实现
  • 2025 年养老院机构口碑最新推荐榜:医养康护一体化服务重磅揭晓,失能失智照护优选品牌全解析失能老人住/陪伴式/失智失能照护养老院公司推荐
  • 2025年散热器铝型材排行榜单
  • 2025年IGBT锡膏企业口碑推荐榜单
  • Java 反射机制深度剖析:性能与安全性的那些坑 - 教程
  • 2025年推拉雨棚定做厂家口碑推荐榜单
  • 2025年口碑好的衣柜平薄铰链厂家最新推荐排行榜
  • 2025年口碑好的少儿编程项目用户满意度榜
  • 《C++ Web 自动化测试实战:常用函数全解析与场景化应用指南》 - 实践
  • 从入门到精通【Redis】Redis 典型应⽤ --- 分布式锁 - 指南
  • 2025年智能中高考加盟电话供应商排名
  • 2025年评价高的少儿编程加盟投资热度榜
  • 无电脑也能成为漏洞猎人:我的实战经验分享
  • 2025年节能门窗品牌哪家靠谱
  • 2025-11-15 早报新闻