PerfView专题 (第九篇):洞察 C# 中的 LOH 内存碎片化

一:背景

内存泄漏 的系列问题中,有一类问题是 内存碎片化 导致的,而且这种更容易发生在 LOH 上,因为它默认不开启 对象压缩,一般遇到这种情况,优先让朋友执行下面的代码应急。


GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();    

后续再研究问题根源,这篇我们就来聊一聊如何用 PerfView 神器帮助我们寻找 内存碎片化 的根源。

二:碎片化洞察

1. WinDbg 的局限

为了方便讲述,先上一段造成 LOH内存碎片化 的测试代码。


    internal class Program
    {
        static void Main(string[] args)
        {
            Test();

            Console.ReadLine();
        }

        public static List<byte[]> list = new List<byte[]>();

        static void Test()
        {
            for (int i = 0; i < 50000; i++)
            {
                if (i % 2 == 0)
                {
                    list.Add(new byte[85000 * 2]);
                    list[i] = null;
                }
                else
                {
                    list.Add(new byte[85000]);
                }
            }

            Console.WriteLine("5w 数据插入完毕!");
        }
    }

代码逻辑非常简单,就是间隔释放其中的 byte[] 对象,让 Free 和 Live 对象成交错状, 可以用 WinDbg 观察如下:


0:009> !dumpheap 
         Address               MT     Size
...
00000000f0cd6028 000000000050ff60   170088 Free
00000000f0cff890 00007ffdb4a25490    85024     
00000000f0d144b0 000000000050ff60   170088 Free
00000000f0d3dd18 00007ffdb4a25490    85024     
00000000f0d52938 000000000050ff60   170088 Free
00000000f0d7c1a0 00007ffdb4a25490    85024     
00000000f0d90dc0 000000000050ff60   170088 Free
00000000f0dba628 00007ffdb4a25490    85024     
00000000f0dcf248 000000000050ff60   170088 Free
00000000f0df8ab0 00007ffdb4a25490    85024     
00000000f0e0d6d0 000000000050ff60   170088 Free
00000000f0e36f38 00007ffdb4a25490    85024     
00000000f0e4bb58 000000000050ff60   170088 Free
00000000f0e753c0 00007ffdb4a25490    85024     
00000000f0e89fe0 000000000050ff60   170088 Free
00000000f0eb3848 00007ffdb4a25490    85024     
00000000f0ec8468 000000000050ff60   170088 Free
00000000f0ef1cd0 00007ffdb4a25490    85024     
00000000f0f068f0 000000000050ff60   170088 Free
00000000f0f30158 00007ffdb4a25490    85024     
00000000f0f44d78 000000000050ff60   170088 Free
...

虽然用 WinDBG 可以轻松找出,但这里有一个非常大的局限,就是你不知道 Free 对象生前是什么东西,往往这时候就只能用 db,dc,du 看内存地址,在无计可施的情况下, PerfView 就可以大显威龙了。

2. PerfView 洞察

接下来我们打开PerfView,采用默认设置启动收集,稍等之后,点击 Memory -> GCStats 项,观察 LOH Frag % 列,如下图所示:

从图中的 LOH Frag % 列可以看出碎片化确实蛮高的,接下来我们就是找 Free 块生前是什么东西,如果能记录到 Free 生成是由谁分配的那该有多好呀!!!

哈哈,在 PerfView 中还真有这么一个视图叫 Gen 2 Object Deaths Stacks,如下图所示:

从名字上就能看到,这个视图记录的是 LOH 上那些已经死亡对象的生前 Stack,当然了,这是按权重计算的,如果是 Event 模式产生的就好了,那会记录所有的对象分配。

接下来双击 Gen 2 Object Deaths Stacks 再选中我们的应用程序,可以看到权重占比最高的是 System.Byte[] 对象,如下图所示:

接下来右键点击 Goto -> Goto Item in Callers 按钮,可以看到占比最高的是 Program.Test() 分配所致,高达 9396 个,如下图所示:

接下来就是调研 Program.Test() 方法,找出最后被 Free 的原因, 这就是 PerfView 和 WinDbg 双剑合璧的威力。

posted @ 2022-08-23 16:35  一线码农  阅读(952)  评论(0编辑  收藏  举报