LOH碎片 – 2015年更新

有很多关于.NET LOH的信息,并且已在各种文章中对其进行了解释。 然而,似乎有些文章缺乏一点精确性。

过时的信息

在Brian Rasmussen的回答(2009年),微软的项目经理 ,他说限制是85000字节。 他还告诉我们,有一个更加奇怪的double[]案例,大小为1000个元素。 CLR团队成员Maoni Stephens(MSDN,2008)也提出了相同的85000限制。

在评论中,Brian Rasmussen变得更加精确,让我们知道它可以用85000字节的byte[] –12字节来再现。

2013年更新

Mario Hewardt(“高级Windows调试”的作者)在2013年告诉我们,如果我们告诉它,.NET 4.5.1现在也可以压缩LOH。 由于默认情况下它处于关闭状态,因此问题仍然存在,除非您已经意识到这一点。

2015年更新

我不能再重现byte[]示例了。 使用短蛮力算法,我发现我必须减去24(SOH中的byte[85000-24] ,LOH中的byte[85000-24] ):

  static void Main(string[] args) { int diff = 0; int generation = 3; while (generation > 0) { diff++; byte[] large = new byte[85000-diff]; generation = GC.GetGeneration(large); } Console.WriteLine(diff); } 

我也无法重现double[]语句。 暴力强迫给我10622个元素作为边界(在SOH中为double[10622]在LOH中为double[10622] ):

  static void Main(string[] args) { int size = 85000; int step = 85000/2; while (step>0) { double[] d = new double[size]; int generation = GC.GetGeneration(d); size += (generation>0)?-step:step; step /= 2; } Console.WriteLine(size); } 

即使我编译旧.NET框架的应用程序,也会发生这种情况。 它也不依赖于Release或Debug构建。

如何解释变化?

byte[]示例中从12更改为24可以通过CPU架构从32位更改为64位来解释。 在为x64或AnyCPU编译的程序中,.NET开销从2 * 4字节(4字节对象标题+ 4字节方法表)增加到2 * 8字节(8字节对象标题+ 8字节方法表)。 此外,该数组的长度属性为4字节(32位),而8字节(64位)。

对于double[]示例,只需使用计算器:85000字节/ 64位用于double类型= 10625项,已经接近。 考虑到.NET开销,结果是(85000字节 – 24字节)/每双8字节= 10622双精度。 所以没有特别处理double[]了。

顺便说一句,我之前从未找到任何关于LOH碎片的工作演示,所以我自己写了一篇。 只需为x86编译以下代码并运行它。 它甚至包括一些调试提示。

编译为x64时,它将无法正常工作,因为Windows可能会增加页面文件的大小,因此后续分配20 MB内存可能会再次成功。

 class Program { static IList small = new List(); static IList big = new List(); static void Main() { int totalMB = 0; try { Console.WriteLine("Allocating memory..."); while (true) { big.Add(new byte[10*1024*1024]); small.Add(new byte[85000-3*IntPtr.Size]); totalMB += 10; Console.WriteLine("{0} MB allocated", totalMB); } } catch (OutOfMemoryException) { Console.WriteLine("Memory is full now. Attach and debug if you like. Press Enter when done."); Console.WriteLine("For WinDbg, try `!address -summary` and `!dumpheap -stat`."); Console.ReadLine(); big.Clear(); GC.Collect(); Console.WriteLine("Lots of memory has been freed. Check again with the same commands."); Console.ReadLine(); try { big.Add(new byte[20*1024*1024]); } catch(OutOfMemoryException) { Console.WriteLine("It was not possible to allocate 20 MB although {0} MB are free.", totalMB); Console.ReadLine(); } } } }