Подтвердить что ты не робот

.NET OutOfMemoryException

Почему это:

class OutOfMemoryTest02
{
    static void Main()
    {
        string value = new string('a', int.MaxValue);
    }
}

Выбросить исключение; но это не будет:

class OutOfMemoryTest
{
    private static void Main()
    {
        Int64 i = 0;
        ArrayList l = new ArrayList();
        while (true)
        {
            l.Add(new String('c', 1024));

            i++;
        }
    }
}

В чем разница?

4b9b3361

Ответ 1

Вы искали int.MaxValue в документах? это эквивалент 2 ГБ, который, вероятно, больше оперативной памяти, чем у вас есть для непрерывного блока символов "а", - вот что вы просите здесь.

http://msdn.microsoft.com/en-us/library/system.int32.maxvalue.aspx

Ваш бесконечный цикл в конечном итоге приведет к тому же исключению (или другому, косвенно связанному с чрезмерным использованием ОЗУ), но это займет некоторое время. Попробуйте увеличить 1024 до 10 * 1024 * 1024, чтобы быстрее воспроизвести симптом в случае цикла.

Когда я запускаю этот большой размер строки, я получаю исключение менее чем через 10 секунд после 68 циклов (проверка i).

Ответ 2

Ваш

new string('a', int.MaxValue);

выбрасывает OutOfMemoryException просто потому, что .NET string имеет ограничение длины. В разделе "Замечания" в в документах MSDN говорится:

Максимальный размер объекта String в памяти составляет 2 ГБ или около 1 миллиарда символов.

В моей системе (.NET 4.5 x64) new string('a', int.MaxValue/2 - 31) срабатывает, тогда как new string('a', int.MaxValue/2 - 32) работает.

В вашем втором примере бесконечный цикл выделяет блоки размером ~ 2048 байт, пока ваша ОС не сможет выделить больше блока в виртуальном адресном пространстве. Когда это будет достигнуто, вы получите также OutOfMemoryException.

(~ 2048 байт = 1024 символа * 2 байта в кодовой строке UTF-16 + служебные байты строки)

Попробуйте отличную статью Эрика.

Ответ 3

Потому что int.MaxValue - 2 147 483 647 или 2 гигабайта, которые необходимо распределять смежно.

Во втором примере ОС нужно всего лишь найти 1024 байта для распределения каждый раз и может поменяться на жесткий диск. Я уверен, что если вы оставите его достаточно долго, вы окажетесь в темном месте:)

Ответ 4

Объект String может использовать резервный общий пул строк для уменьшения использования памяти. В первом случае вы создаете одну строку с несколькими гигабайтами. Во втором случае, скорее всего, компилятор выполняет автоинтерминирование строки, поэтому вы генерируете строку длиной 1024 байта, а затем ссылаетесь на эту же строку много раз.

Говоря об этом, ArrayList этого размера должен вывести вас из памяти, но, скорее всего, вы не допустили, чтобы код работал достаточно долго, чтобы у него не хватило памяти.

Ответ 5

Второй снипп также выйдет из строя. Это просто занимает больше времени, так как он потребляет память намного медленнее. Обратите внимание на свет доступа к жесткому диску, он яростно мигает, а Windows выгружает страницы из ОЗУ, чтобы освободить место. Первый конструктор строк сразу же выходит из строя, поскольку диспетчер кучи не позволяет вам выделять 4 гигабайта.

Ответ 6

Обе версии вызовут исключение OOM, это просто (на 32-битной машине) вы сразу получите его с первой версией при попытке выделить "один" очень большой объект.

Вторая версия займет гораздо больше времени, так как будет много хлопот, чтобы добраться до условия OOM для нескольких факторов:

  • Вы будете выделять миллионы мелких объектов, которые могут быть доступны GC. После того, как вы начнете подвергать систему под давлением, GC проведет чрезмерное количество поколений сканирования с миллионами и миллионами объектов. Это займет много времени и начнет играть хаос с пейджингом, так как холодная и горячая память будет постоянно выгружается и просматривается, когда отсканированы поколения.

  • Будет разбита страница, поскольку GC сканирует миллионы объектов в поколениях, чтобы попытаться освободить память. Сканирование приведет к постоянному выгружению огромного объема памяти.

Измельчение приведет к тому, что система перевернется на верхнюю часть обработки, и поэтому условие OOM займет много времени. Большая часть времени будет потрачена на GC и пейджинг для второй версии.

Ответ 7

В первом примере вы пытаетесь создать строку 2g за один раз

Во втором примере вы добавляете 1k к массиву. Для достижения такого же уровня потребления вам потребуется более 2 миллионов циклов.

И он также не все сохраняется сразу, в одной переменной. Таким образом, некоторые из ваших возможностей использования памяти могут сохраняться на диске, чтобы освободить место для новых данных. Я думаю.

Ответ 8

Поскольку один объект не может иметь более 2 ГБ:

Сначала немного фона; в версии 2.0 для среды .NET(CLR) мы приняли осознанное дизайнерское решение, чтобы поддерживать максимальный размер объекта, разрешенный в GC Heap, на 2 ГБ, даже в 64-битной версии среды выполнения

В первом примере вы пытаетесь выделить один объект, который на 2 ГБ, с служебными данными объекта (8 байтов?), он просто слишком большой.

Я не знаю, как ArrayList работает внутри, но вы выделяете несколько объектов по 2 ГБ каждый, а ArrayList - насколько мне известно, - содержит только указатели, которые являются 4 (8 на x64?) байтах, независимо от того, насколько большой объект они указывают на.

Чтобы процитировать другую статью:

Кроме того, объекты, имеющие ссылки на другие объекты, хранят только ссылку. Поэтому, если у вас есть объект, который содержит ссылки на три других объекта, размер памяти составляет всего 12 дополнительных байтов: один 32-разрядный указатель на каждый из ссылочных объектов. Неважно, насколько значителен объект, на который ссылается.

Ответ 9

Одна из причин, по которой ваша система может остановиться, - это то, что код .NET работает ближе к металу, и вы находитесь в жесткой петле, которая должна потреблять 100% процессор, если приоритет процесса позволяет это сделать. Если вы хотите, чтобы приложение не потребляло слишком много CPU, когда оно выполняет сжатый цикл, вы должны добавить что-то вроде System.Threading.Thread.Sleep(10) до конца цикла, который принудительно возвращает время обработки другим потокам.

Одно существенное различие между JVM и .NET CLR (Common Language Runtime) заключается в том, что CLR не ограничивает размер вашей памяти в системе x64 (в 32-битных приложениях, без знака Large Address Aware, ограничения ОС любое приложение на 2 ГБ из-за ограничений адресации). Компилятор JIT создает собственный код окна для вашей архитектуры обработки, а затем запускает его в той же области, что и любое другое приложение Windows. JVM - это более изолированная песочница, которая ограничивает приложение заданным размером в зависимости от параметров конфигурации/командной строки.

Что касается различий между двумя алгоритмами:

Создание одиночной строки не может завершиться неудачей при работе в среде x64 с достаточной непрерывной памятью, чтобы выделить 4 ГБ, чтобы содержать символы int.MaxValue(строки .NET по умолчанию Unicode, для которых требуется 2 байта на символ). 32-разрядное приложение всегда терпит неудачу, даже если установлен флаг большого адреса Aware, потому что максимальная память по-прежнему составляет 3,5 ГБ).

В то время как во время цикла ваш код, скорее всего, будет потреблять более общую память, если у вас есть много доступных, прежде чем выбрасывать исключение, потому что ваши строки могут быть выделены меньшими фрагментами, но в конечном итоге это может привести к ошибке (хотя, если вы есть много ресурсов, это может произойти в результате того, что ArrayList превышает максимальное количество элементов в массиве, а не неспособность выделить новое пространство для небольшой строки). Кент Мурра также правильна в отношении интернирования строк; вам потребуется либо рандомизировать длину строки, либо содержимое символа, чтобы избежать интернирования, иначе вы просто создаете указатели на одну и ту же строку. Рекомендация Steve Townsend по увеличению длины строки также заставила бы найти достаточно большие непрерывные блоки памяти, чтобы это происходило быстрее, что позволит сделать это быстрее.

EDIT:

Думаю, что я бы дал некоторые ссылки, которые люди могут найти удобными для понимания памяти .NET:

Эти две статьи немного старше, но очень хорошие по глубине:

Сбор мусора: автоматическое управление памятью в Microsoft.NET Framework

Сбор мусора Часть 2: Автоматическое управление памятью в Microsoft.NET Framework

Это блоги из разработчика .NET Garbage Collection для получения информации о более новой версии управления памятью .NET:

Итак, что нового в CLR 4.0 GC?

CLR 4.5: Маони Стивенс - серверная фонограмма GC

Этот SO-вопрос может помочь вам наблюдать за внутренней работой памяти .NET:

Инструменты профилирования .NET