PerfView专题 (第十一篇):使用 Diff 功能洞察 C# 内存泄漏增量 ...

饭宝  金牌会员 | 2022-9-16 17:21:59 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 576|帖子 576|积分 1728

一:背景

去年 GC架构师 Maoni 在 (2021 .NET 开发者大会)

[https://ke.segmentfault.com/course/1650000041122988/section/1500000041123017] 上演示过 PerfView 的 Diff 功能来寻找内存增量,个人感觉这个功能非常不错,简单省事,所以这里就整合到 PerfView 专题中,分享一下给大家。
二:洞察内存增量

1. 什么是内存增量

其实非常好理解,就是当你的程序出现了内存泄漏,你可以在程序内存增长的过程中截取两个 dump 文件,然后通过 PerfView 观察其中的内存增量是什么? 帮助我们快速找出可能被泄漏的对象。
当然你用 WinDBG 的话也是没有问题的,只不过需要用肉眼扫一下而已,接下来举两个例子说明一下。
2. 静态集合的内存泄漏

很多 dump 的内存泄漏,源自于里面的某一个 static 变量无限堆积所致,为了方便说明,先上一段测试代码。
  1.     internal class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             Task.Run(RunTest);
  6.             Console.ReadLine();
  7.         }
  8.         public static List<string> my_big_list = new List<string>();
  9.         static void RunTest()
  10.         {
  11.             for (int i = 0; i < 50000; i++)
  12.             {
  13.                 my_big_list.Add(string.Join(",", Enumerable.Range(0, 10000)));
  14.                 Console.WriteLine(i);
  15.             }
  16.         }
  17.     }
复制代码
接下来在程序的运行过程中,我们分别截取两个 dump 文件, 点击菜单栏的 Memory -> Take Heap Snapshot 按钮,在 Filter 中搜索需要采集的进程,然后点击 Dump GC Heap 即可,参考如下图:

稍等片刻,你就会看到两个 gcdump 文件,这个和普通的 dump 是不一样的,算是 PerfView 专用的轻量级 dump 文件,截图如下:

接下来点击两个 gcdump 中的 Heap Stacks,对比 inc% 列后发现,内存都被一个叫 my_big_list 变量给吃掉了,前者的count为 10509, 后者是 15172,截图如下:

虽然肉眼可以简单观察,但这里可以使用专业的 Diff 功能,让 PerfView 帮我洞察 栈 的总体增量差异,点击菜单栏中的 Diff -> With Baseline: Heap Stacks [.....] 按钮,即让本 gcdump 和另一个 gcdump 做比较,截图如下:

不过要注意的是,这两个窗口一定要打开,这个是比较坑的,哈哈,接下来就会看到如下图:

从图中可以清晰的看到,这两个 dump 的增量主要来自于 my_big_list 集合,往细处说就是 string 增长了 4663 个。
3. 事件event泄漏

我们再看一个事件泄漏的例子,参考如下代码:
  1.     // event 泄漏
  2.     class Program
  3.     {
  4.         static event Action TestEvent;
  5.         static void Main(string[] args)
  6.         {
  7.             var memory = new TestAction();
  8.             //handle 泄漏
  9.             for (int i = 0; i < int.MaxValue; i++)
  10.             {
  11.                 TestEvent += memory.Run;
  12.                 if (i % 500 == 0)
  13.                 {
  14.                     Console.WriteLine(i);
  15.                 }
  16.             }
  17.             Console.ReadLine();
  18.         }
  19.         public static void OnTestEvent()
  20.         {
  21.             if (TestEvent != null)
  22.             {
  23.                 TestEvent();
  24.             }
  25.             else
  26.             {
  27.                 Console.WriteLine("Test Event is null");
  28.             }
  29.         }
  30.         class TestAction
  31.         {
  32.             public void Run()
  33.             {
  34.                 Console.WriteLine("TestAction Run.");
  35.             }
  36.         }
  37.     }
复制代码
将程序运行起来,用 Process Explorer 抓两个 dump 文件下来,然后点击 Memory -> Take Heap Snapshot From Dump 按钮,截图如下:

在弹出的对话框中设置需要提取的 dump 文件,稍等片刻就会生成如下两个 gcdump 文件,截图如下:

接下来将两个 gcdump 都打开,发现内存都被程序中的一个叫 TestEvent 占用了,如下图所示:

接下来就可以使用 Diff 对比功能了,可以观察到,TestEvent 下面的 Action 增量了将近 700w 个,截图如下:

这里稍微说一下,为什么会增量 700w 的 Action,这主要是因为 event 是一个多播委托,内部有一个 Action 集合,也正是这个 Action 集合 在无限膨胀。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

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

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

饭宝

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表