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

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

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

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

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

Обновление 2013

Марио Хьюардт (автор 'Advanced Windows Debugging') сказал нам в 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[] заявление. Брутфорс дает мне 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-фреймворков. Это также не зависит от версии выпуска или отладки.

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

1 ответ

Решение

Изменение с 12 на 24 в byte[] Пример можно объяснить изменением архитектуры процессора с 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 МБ памяти может снова быть успешным.

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();
            }
        }
    }
}
Другие вопросы по тегам