4

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

 1 year ago
source link: https://www.cnblogs.com/huangxincheng/p/16625160.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

214741-20220825175017107-33919165.png

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

二:洞察内存增量

1. 什么是内存增量

其实非常好理解,就是当你的程序出现了内存泄漏,你可以在程序内存增长的过程中截取两个 dump 文件,然后通过 PerfView 观察其中的内存增量是什么? 帮助我们快速找出可能被泄漏的对象。

当然你用 WinDBG 的话也是没有问题的,只不过需要用肉眼扫一下而已,接下来举两个例子说明一下。

2. 静态集合的内存泄漏

很多 dump 的内存泄漏,源自于里面的某一个 static 变量无限堆积所致,为了方便说明,先上一段测试代码。


    internal class Program
    {
        static void Main(string[] args)
        {
            Task.Run(RunTest);
            Console.ReadLine();
        }

        public static List<string> my_big_list = new List<string>();

        static void RunTest()
        {
            for (int i = 0; i < 50000; i++)
            {
                my_big_list.Add(string.Join(",", Enumerable.Range(0, 10000)));
                Console.WriteLine(i);
            }
        }
    }

接下来在程序的运行过程中,我们分别截取两个 dump 文件, 点击菜单栏的 Memory -> Take Heap Snapshot 按钮,在 Filter 中搜索需要采集的进程,然后点击 Dump GC Heap 即可,参考如下图:

214741-20220825175017090-2083469748.png

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

214741-20220825175017103-66363567.png

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

214741-20220825175017129-1655542943.png

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

214741-20220825175017072-1398489285.png

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

214741-20220825175017092-1840064539.png

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

3. 事件event泄漏

我们再看一个事件泄漏的例子,参考如下代码:


    // event 泄漏
    class Program
    {
        static event Action TestEvent;

        static void Main(string[] args)
        {
            var memory = new TestAction();

            //handle 泄漏
            for (int i = 0; i < int.MaxValue; i++)
            {
                TestEvent += memory.Run;

                if (i % 500 == 0)
                {
                    Console.WriteLine(i);
                }
            }

            Console.ReadLine();
        }

        public static void OnTestEvent()
        {
            if (TestEvent != null)
            {
                TestEvent();
            }
            else
            {
                Console.WriteLine("Test Event is null");
            }
        }

        class TestAction
        {
            public void Run()
            {
                Console.WriteLine("TestAction Run.");
            }
        }
    }

将程序运行起来,用 Process Explorer 抓两个 dump 文件下来,然后点击 Memory -> Take Heap Snapshot From Dump 按钮,截图如下:

214741-20220825175017093-539510766.png

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

214741-20220825175017107-1614264378.png

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

214741-20220825175017119-324244369.png

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

214741-20220825175017104-27109359.png

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK