Как я могу получить OutOfMemoryException в .Net? - PullRequest
1 голос
/ 08 марта 2012

В разрабатываемом приложении, требующем памяти, я сталкиваюсь с исключениями OutOfMemoryException Я ожидал, что никогда не будет такого рода исключений с виртуальной памятью. Почему, если требуется больше ОЗУ, чем доступно, ОС не использует HD в качестве ОЗУ?

Ответы [ 2 ]

10 голосов
/ 08 марта 2012

Вы можете получить исключение OutOfMemoryException разными способами. В 32-битных окнах процесс может использовать 2 ГБ виртуального адресного пространства (я считаю, что в 64-битных приложениях это 1 ТБ). Имейте в виду, что это , а не ОЗУ, оно совершенно другое, следовательно, «виртуальное». Так что забудь про оперативку. Все, что делает ОЗУ, это ускоряет процесс. Диспетчер памяти Windows решает, будет ли используемая вашей программой память находиться в ОЗУ или на диске. Когда у вас открыто много программ, в физическую память загружается меньше памяти, а на диске - больше. Ваша производительность будет ужасной, но у вас не будет исключения OOM.

Самый распространенный способ получения исключения OOM в .NET - попытаться выделить достаточно большой объем данных (что-то вроде байтового массива) там, где недостаточно непрерывных доступных страниц памяти. чтобы отобразить это, где я подозреваю, что ваша проблема лежит. Это означает, что можно получить исключение нехватки памяти, даже если вы запрашиваете новый объект, который увеличил бы виртуальное адресное пространство до объема более 2 ГБ.

«Но подождите!» , можно сказать, «Когда работает сборщик мусора, память сжимается, поэтому в конце кучи всегда есть непрерывное пространство!»

Это правда, по большей части. GC будет собирать данные из кучи, которую можно рассматривать как две кучи, кучу малых объектов (SOH) и их из кучи больших объектов (LOH). Большинство объектов, поскольку они довольно маленькие, будут жить в основной куче поколений. Однако, если объект превышает определенный порог размера (это было 85 КБ, но, возможно, изменился), он вместо этого выделяется на LOH.

Когда память восстанавливается из LOH, она не уплотняется, как SOH. Это было бы слишком дорого, чтобы память в LOH была сжата. Выделение памяти достаточно дорогое, и производительность приложения пострадает. Это означает, что ваша куча может и часто становится фрагментированной (это значительно улучшилось в среде выполнения .NET 4.0, по сравнению с версиями, совместимыми с 2.0).

  • Допустим, ваша куча LOH выглядит так до GC: * ​​1024 *
* = object
- = free space

|**|***|*|**|*|******|****|**|*****|*******|**|*|***|*****|***|**|--
 ^        ^  ^                ^             ^        ^         ^  marked for GC
  • это будет выглядеть, как только полный GC будет сделан:
* = object
- = free space

|--|***|*|-- -|******|****|**|-----|*******|--|*|***|-----|***|----

Теперь я хочу выделить массив ******** длиной. Среда выполнения попытается найти место там, где оно может поместиться. Это не так. Что делает среда выполнения? Что ж, если предположить, что приведенное выше представляет все ваше виртуальное пространство объемом 2 ГБ и больше памяти не может быть выделено, то будет сгенерировано исключение OOM. У вас есть свободных страниц памяти, но недостаточно смежных .

Понятия не имею, что делает ваше приложение, но вот несколько вещей, которые нужно проверить:

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

  • Получаете ли вы размер распределения на основе пользовательских или других данных? Например, если вы читаете заголовок файла, в котором указывается длина записи, проверяете ли вы, что размер записи правильный? Я сам столкнулся с ошибками, прочитал неправильный адрес в файле и попытался выделить 2 ТБ памяти. Проверьте это безумие.

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

Есть только несколько вещей, о которых я могу думать, и это, конечно, не является исчерпывающим.Другим способом исчерпания памяти, помимо попытки адресовать более 2 ГБ адресного пространства, был бы файл полной страницы, но это не так часто.

Некоторые полезные инструменты:

  • WinDbg (входит в Windows SDK, как часть средств отладки для Windows) - вы можете использовать это для просмотра статистики о куче.Мой личный фаворит - !dumpheap -stat и !dumpheap -type [type].Я могу использовать это для быстрого поиска типов, которые используют много памяти.

  • CLRProfiler - Легче использовать, чем WinDbg, с меньшим количеством функций.Это дает вам прекрасный графический взгляд на распределение памяти, и вы даже можете увидеть, сколько дескрипторов GC осталось при выходе из приложения.График раскрашен по типу, поэтому он дает обзор того, какие типы используют больше всего памяти в вашей куче.

0 голосов
/ 08 марта 2012

Это можно получить, если вы превысили максимальный размер кучи или адресное пространство.

...