一:背景
在 内存泄漏 的系列问题中,有一类问题是 内存碎片化 导致的,而且这种更容易发生在 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 双剑合璧的威力。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |