蕙碎片年 - 2015年更新碎片

2023-09-03 00:41:37 作者:反映你心

有很多可用的信息有关.NET蕙,它已经在各种物品被解释。然而,似乎有些文章没有一点precision。

There is a lot of information available about the .NET LOH and it has been explained in various articles. However, it seems that some articles lack a bit of precision.

在布莱恩·拉斯穆森的回答(2009年),在微软项目经理,他说,限制为85000字节。他还让我们知道,有一个更奇怪的情况下的双重[] 以1000元的尺寸。同样的85000限制由猫腻斯蒂芬斯(MSDN,2008年),在CLR的成员指出团队。

In Brian Rasmussen's answer (2009), program manager at Microsoft, he says the limit is 85000 bytes. He also let's us know that there is an even more curious case of double[] with a size of 1000 elements. The same 85000 limit is stated by Maoni Stephens (MSDN, 2008), member of the CLR team.

在评论,布莱恩·拉斯穆森变得更加准确,让我们知道,它可以进行复制,且字节[] 的85000字节 - 12字节。

In the comments, Brian Rasmussen becomes even more exact and let's us know that it can be reproduced with a byte[] of 85000 bytes - 12 bytes.

马里奥Hewardt 告诉我们,在2013年的.NET 4.5.1现在可以压缩蕙还有,如果我们告诉它这样做(高级Windows调试的作者)。因为它是默认关闭的,问题依然存在,除非你意识到这一点了。

Mario Hewardt (author of 'Advanced Windows Debugging') told us in 2013 that .NET 4.5.1 can now compact the LOH as well, if we tell it to do so. Since it is turned off by default, the problem remains unless you're aware of it already.

我无法重现字节[] 的例子了。随着短蛮力算法,我发现我必须减去24,而不是(字节[84999-24] 在SOH,字节[85000 -24] 陆恭蕙):

I can't reproduce the byte[] example any more. With a short brute-force algorithm, I found out that I have to subtract 24 instead (byte[84999-24] in SOH, byte[85000-24] in LOH):

    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);
    }

我也无法重现双[] 语句。暴力破解给了我10622元为界(双[10621] 在SOH,双[10622] 陆恭蕙):

I also couldn't reproduce the double[] statement. Brute-forcing gives me 10622 elements as the border (double[10621] in SOH, double[10622] in LOH):

    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框架应用程序。它也并不依赖于发行或调试版本。

This happens even if I compile the application for older .NET frameworks. It also does not depend on Release or Debug build.

如何变化来解释?

推荐答案

从12到24的变化字节[] 例如,可以通过在CPU的变化解释建筑从32到64位。在编译为64位或值为anycpu程序,从12到24个字节的.NET对象头尺寸的增加。

The change from 12 to 24 in the byte[] example can be explained by the change in CPU architecture from 32 to 64 bit. In programs compiled for x64 or AnyCPU, the .NET object header size increases from 12 to 24 bytes.

对于双[] 例如,只用一个计算器:85000字节/ 64位为双类型= 10625项,这已经是接近。考虑到.NET对象头,结果是(85000-24)/ 8 = 10622.所以有的任何特殊处理的双[] 了。

For the double[] example, just use a calculator: 85000 bytes / 64 bit for the double type = 10625 items, which is already close. Considering the .NET object header, the result is (85000-24) / 8 = 10622. So there is no special handling of double[] any more.

顺便说一句,我从来没有发现任何工作示范蕙碎片之前,所以我写了一个自己。刚刚编译以下code x86和运行它。它甚至还包括一些调试提示。

BTW, I have never found any working demonstration for LOH fragmentation before, so I wrote one myself. Just compile the following code for x86 and run it. It even includes some debugging hints.

在为64位编译因为Windows可能会增加页面文件的大小是无法进行为好,这样的20 MB内存随后的分配可能会再次获得成功。

It won't work as well when compiled as x64 since Windows might increase the size of the pagefile, so the subsequent allocation of 20 MB memory could be successful again.

class Program
{
    static IList<byte[]> small = new List<byte[]>();
    static IList<byte[]> big = new List<byte[]>(); 

    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();
            }
        }
    }
}