Фрагментация LOH - обновление до 2015 года

Существует много информации о .NET LOH, и это объясняется в различных статьях. Тем не менее, кажется, что в некоторых статьях не хватает точности.

Устаревшая информация

В Брайан Расмуссен отвечает (2009), менеджер программ в Microsoft, он говорит, что предел составляет 85000 байт. Он также сообщил нам, что существует еще более любопытный случай double[] с размером 1000 элементов. Тот же предел в 85000 указан Маони Стивенс (MSDN, 2008), член команды CLR.

В комментариях Брайан Расмуссен становится еще точнее и дайте нам знать, что его можно воспроизвести с помощью byte[] из 85000 байт - 12 байт.

Обновление 2013 года

Марио Хьюардт (автор "Расширенная отладка Windows" ) рассказал нам в 2013 году, что .NET 4.5.1 теперь может сжать LOH как хорошо, если мы скажем это сделать. Поскольку он по умолчанию отключен, проблема остается, если вы уже не знаете об этом.

2015 обновление

Я больше не могу воспроизвести пример byte[]. С помощью короткого алгоритма грубой силы я обнаружил, что вместо этого вычесть 24 (byte[84999-24] в SOH, byte[85000-24] в 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);
    }

Я также не смог воспроизвести оператор double[]. Brute-forcing дает мне 10622 элемента в качестве границы (double[10621] в SOH, double[10622] в 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-фреймворков. Это также не зависит от сборки Release или Debug.

Как объяснить изменения?

Ответ 1

Изменение от 12 до 24 в примере byte[] может быть объяснено изменением архитектуры ЦП от 32 до 64 бит. В программах, скомпилированных для x64 или AnyCPU, размер заголовка объекта .NET увеличивается от 12 до 24 байтов.

Для примера double[] просто используйте калькулятор: 85000 байт /64 бит для двойного типа = 10625 элементов, который уже близок. Учитывая заголовок объекта .NET, результатом является (85000-24)/8 = 10622. Таким образом, нет специальной обработки double[].

Кстати, я до сих пор не обнаружил какой-либо рабочей демонстрации для фрагментации LOH, поэтому сам написал. Просто скомпилируйте следующий код для x86 и запустите его. Он даже содержит некоторые подсказки для отладки.

Он не будет работать, если компилироваться как x64, поскольку Windows может увеличить размер файла подкачки, поэтому последующее распределение памяти на 20 МБ может быть успешным снова.

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