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

Когда все в порядке, чтобы поймать OutOfMemoryException и как с ним справиться?

Вчера я принял участие в обсуждении SO, посвященном OutOfMemoryException, а также за и против его обработки (С# try {} catch {}).

Мои профи для обработки:

  • Тот факт, что OutOfMemoryException был брошен, обычно не означает, что состояние программы было повреждено;
  • В соответствии с документацией "следующие инструкции Microsoft промежуточного (MSIL) вызывают OutOfMemoryException: box, newarr, newobj", который просто (обычно) означает, что CLR попыталась найти блок памяти определенного размера и не смогла этого сделать; это не означает, что ни один байт не остался в нашем распоряжении;

Но не все люди согласны с этим и размышляют о неизвестном состоянии программы после этого исключения и невозможности сделать что-то полезное, поскольку для этого потребуется еще больше памяти.

Поэтому мой вопрос: каковы серьезные причины не обрабатывать OutOfMemoryException и немедленно сдаваться, когда это происходит?

Отредактировано: Считаете ли вы, что OOME является столь же фатальным, как ExecutionEngineException?

4b9b3361

Ответ 1

Мы все пишем разные приложения. В приложении WinForms или ASP.Net я бы, вероятно, просто зарегистрировал исключение, уведомил пользователя, попытался сохранить состояние и выключить/перезапустить. Но, как отметил Игорь в комментариях, это вполне может быть связано с созданием приложения для редактирования изображений, а процесс загрузки 100-го 20-мегабайтного RAW-изображения может подтолкнуть приложение к краю. Вы действительно хотите, чтобы использование потеряло всю их работу от чего-то столь же простого, как сказать. "Извините, не удалось загрузить больше изображений в это время".

Другой распространенный экземпляр, который может быть полезен для исключения исключений из памяти, заключается в пакетной обработке задних рядов. У вас может быть стандартная модель загрузки файлов с несколькими мегабайтами в память для обработки, но в один прекрасный день в один прекрасный день загружается файл с несколькими гигабайтами. Когда происходит нехватка памяти, вы можете записать сообщение в очередь уведомлений пользователя, а затем перейти к следующему файлу.

Да, возможно, что что-то еще может ударить в одно и то же время, но они тоже будут регистрироваться и уведомляться, если это возможно. Если, наконец, GC не сможет обработать больше памяти, приложение все равно будет идти вниз. (GC работает в незащищенном потоке.)

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

К вашему редактированию...

Из-за памяти может быть вызвана неуправляемая фрагментация и закрепление памяти. Это также может быть вызвано большими запросами на распределение. Если бы мы поставили белый флаг и нарисовали линию на песке по таким простым вопросам, в крупных проектах обработки данных ничего не получится. Теперь, сравнивая это с фатальным исключением Engine, хорошо, что вы ничего не можете сделать в момент, когда время выполнения падает над мертвым под вашим кодом. Надеюсь, вы сможете зайти в журнал (но, вероятно, нет), почему ваш код упал на его лицо, чтобы вы могли предотвратить его в будущем. Но, что еще более важно, мы надеемся, что ваш код написан таким образом, чтобы обеспечить безопасное восстановление как можно большего количества данных. Возможно, даже восстановить последнее известное хорошее состояние в своем приложении и, возможно, пропустить повреждающие поврежденные данные и разрешить его вручную обрабатывать и восстанавливать.

В то же время как можно больше имеет повреждение данных, вызванное SQL-инъекцией, версиями программного обеспечения, не использующими синхронизацию, манипулирование указателями, переполнение буфера и многие другие проблемы. Избегая проблемы только потому, что вы считаете, что не можете ее восстановить, это отличный способ дать пользователям сообщения об ошибках как конструктивные, так как обратитесь к своему системному администратору.

Ответ 2

IMO, поскольку вы не можете предсказать то, что вы можете/не можете сделать после OOM (чтобы вы не могли надежно обработать ошибку) или что еще не произошло/не произошло при разворачивании стека туда, где вы находитесь (поэтому BCL не обработал достоверную ошибку), ваше приложение должно теперь считаться поврежденным. Если вы "исправите" свой код, применив это исключение, вы хороните свою голову в песке.

Я мог ошибаться, но для меня это сообщение говорит БОЛЬШАЯ НЕИСПРАВНОСТЬ. Правильное исправление заключается в том, чтобы выяснить, почему вы перепутали память, и укажите, что (например, у вас есть утечка? Можете ли вы переключиться на потоковый API?). Даже переход на x64 здесь не волшебная пуля; массивы (и, следовательно, списки) по-прежнему ограничены по размеру; и увеличенный ссылочный размер означает, что вы можете исправить числовые ссылки меньше в крышке объекта размером 2 ГБ.

Если вам нужно обработать некоторые данные, и они с удовольствием потерпят неудачу: запустите второй процесс (AppDomain недостаточно хорош). Если он взрывается, оторвите процесс. Проблема решена, и ваш исходный процесс / AppDomain безопасен.

Ответ 3

Некоторые комментаторы отметили, что есть ситуации, когда OOM может быть непосредственным результатом попытки выделить большое количество байтов (графическое приложение, выделение большого массива и т.д.). Обратите внимание, что для этой цели вы можете использовать класс MemoryFailPoint, который вызывает InsufficientMemoryException (сам по себе происходит из OutOfMemoryException). Это можно поймать безопасно, поскольку оно возникает до того, как была сделана фактическая попытка выделить память. Тем не менее, это может только реально уменьшить вероятность OOM, никогда полностью не предотвращать его.

Ответ 4

Все зависит от ситуации.

Совсем недавно я работал над движком 3D-рендеринга в реальном времени. В то время мы загружали всю геометрию для модели в память при запуске, но загружали только текстурные изображения, когда нам нужно было их отображать. Это означало, что в тот день, когда наши клиенты загрузили огромные (2 ГБ) модели, мы смогли справиться. Геометрия заняла менее 2 ГБ, но когда все текстуры были добавлены, было бы > 2 ГБ. Улавливая ошибку из памяти, которая была поднята, когда мы пытались загрузить текстуру, мы смогли продолжить отображение модели, но точно так же, как простая геометрия.

У нас все еще была проблема, если геометрия былa > 2 ГБ, но это была другая история.

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

Ответ 5

Предложите комментарий Кристофера Брумме в "Руководстве по проектированию каркаса" стр .238 (7.3.7 OutOfMemoryException):

На одном конце спектра исключение OutOfMemoryException может быть результатом отказа получить 12 байтов для неявного автобоксинга или отказа JIT кода, необходимого для критического резервного копирования. Эти случаи являются катастрофическими неудачами и в идеале приводят к прекращению процесса. На другом конце спектра OutOfMemoryException может быть результатом потока, запрашивающего массив размером 1 ГБ. Тот факт, что мы провалили эту попытку размещения, не влияет на согласованность и жизнеспособность остальной части процесса.

Печальный факт заключается в том, что CRL 2.0 не может отличить ни одну точку от этого спектра. В большинстве управляемых процессов все OutOfMemoryExceptions считаются эквивалентными, и все они приводят к тому, что управляемое исключение распространяется по потоку. Тем не менее, вы не можете зависеть от выполняемого кода резервного копирования, потому что нам может не удастся выполнить JIT некоторые из ваших методов резервного копирования, иначе нам не удастся выполнить статические конструкторы, необходимые для резервного копирования.

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

Моя лучшая рекомендация в том, что вы относитесь к OutOfMemoryException, как к любому другому исключению приложения. Вы делаете все возможное, чтобы справиться с этим и стабильно расти. В будущем я надеюсь, что CLR сможет лучше отличить катастрофический OOM от массива байтов размером 1 ГБ. Если это так, мы можем спровоцировать прекращение процесса для катастрофических случаев, в результате чего приложение будет иметь дело с менее рискованными. Разбирая все случаи OOM как менее рискованные, вы готовитесь к этому дню.

Ответ 6

Марк Гравелл уже дал отличный ответ; видя, как я частично "вдохновил" этот вопрос, хотел бы добавить одну вещь:

Один из основных принципов обработки исключений никогда не должен вызывать исключение внутри обработчика исключений. (Примечание - повторное бросание специфичного для домена и/или завернутого исключения в порядке, я говорю о неожиданном исключении здесь.)

Существуют всевозможные причины, по которым вам необходимо предотвратить это:

  • В лучшем случае вы маскируете исходное исключение; становится невозможно точно знать, где программа изначально не удалась.

  • В некоторых случаях среда выполнения может просто не справиться с обработкой необработанного исключения в обработчике исключений (скажем, что 5 раз быстрее). Например, в ASP.NET установка обработчика исключений на определенных этапах конвейера и отказ в этом обработчике просто убьет запрос - или разрушит рабочий процесс, я забуду, какой из них.

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

Итак, что это связано с OutOfMemoryException конкретно?

An OutOfMemoryException ничего не говорит о почему не удалось выделить память. Вы можете предположить, что это было потому, что вы пытались выделить огромный буфер, но, возможно, это не так. Возможно, какой-то другой процесс изгоев в системе буквально поглотил все доступное адресное пространство, и у вас нет одного байта слева. Может быть, какой-то другой поток в вашей собственной программе пошатнулся и перешел в бесконечный цикл, выделив новую память на каждой итерации, и этот поток уже давно провалился к моменту окончания OutOfMemoryException в вашем текущем фрейме стека. Дело в том, что вы не знаете, насколько плоха ситуация в памяти, даже если вы думаете, что делаете.

Итак, начните думать об этой ситуации сейчас. Некоторая операция просто провалилась неуточненной точкой в ​​глубине среды .NET и распространилась на OutOfMemoryException. Какую значимую работу вы можете выполнить в своем обработчике исключений, который не включает выделение большего объема памяти? Запись в файл журнала? Это требует памяти. Отобразить сообщение об ошибке? Это занимает еще больше памяти. Отправить оповещение по электронной почте? Даже не думай об этом.

Если вы попытаетесь сделать эти вещи - и потерпите неудачу - тогда вы окажетесь без детерминированного поведения. Возможно, вы замаскируете ошибку вне памяти и получите таинственные сообщения об ошибках с таинственными сообщениями об ошибках, возникающими из всех видов низкоуровневых компонентов, которые вы написали, которые не должны быть в состоянии потерпеть неудачу. По сути, вы нарушили свои собственные программные инварианты, и это будет кошмар для отладки, если ваша программа когда-либо закончится в условиях низкой памяти.

Один из аргументов, представленных мне ранее, заключался в том, что вы могли бы поймать OutOfMemoryException, а затем переключиться на более низкий код памяти, например, меньший буфер или потоковая модель. Однако это "" Обработка ошибок "- это хорошо известный анти-шаблон. Если вы знаете, что собираетесь собирать огромный объем памяти и не уверены, сможет ли система справиться с этим, тогда проверить доступную память, или еще лучше, просто реорганизуйте свой код, чтобы он не нуждался в столь большой памяти сразу. Не полагайтесь на OutOfMemoryException, чтобы сделать это за вас, потому что - кто знает - возможно, распределение будет едва успешным и вызовет кучу ошибок из памяти сразу после вашего обработчика исключений (возможно, в некотором совершенно другом компоненте).

Итак, мой простой ответ на этот вопрос: Никогда.

Мой ласок - ответ на этот вопрос: Это нормально в глобальном обработчике исключений, если вы действительно очень осторожны. Не в блоке try-catch.

Ответ 7

Одной из практических причин для исключения этого исключения является попытка изящного выключения с дружественным сообщением об ошибке вместо трассы исключений.

Ответ 8

Проблема заключается в том, что - в отличие от других исключений - обычно возникает ситуация с низкой памятью, когда возникает исключение (за исключением случаев, когда выделенная память огромна, но вы не знаете, когда вы поймаете исключение).

Поэтому вы должны быть очень осторожны, чтобы не выделять память при обработке этого исключения. И хотя это звучит легко, нет, на самом деле очень сложно избежать выделения памяти и сделать что-то полезное. Поэтому ловить это обычно не очень хорошая идея ИМХО.

Ответ 9

Проблема больше, чем .NET. Почти любое приложение, написанное с пятидесятых годов до настоящего времени, имеет большие проблемы, если память отсутствует.

С виртуальными адресными пространствами проблема была решена, но НЕ решена, потому что даже адресные пространства размером 2 ГБ или 4 ГБ могут стать слишком маленькими. Нет обычных шаблонов для обработки памяти. Может существовать метод предупреждения из памяти, метод паники и т.д., Который, как гарантируется, все еще имеет доступную память.

Если вы получаете OutOfMemoryException из .NET, почти все может быть так. 2 MB все еще доступно, всего 100 байт, что угодно. Я бы не захотел поймать это исключение (кроме выключения без диалога сбоя). Нам нужны лучшие концепции. Тогда вы можете получить MemoryLowException, где вы можете реагировать на всевозможные ситуации.

Ответ 10

Введите код, не увлекайте JVM. Когда VM смиренно сообщает вам, что запрос на распределение памяти не удался, лучшим вариантом является отказ от состояния приложения, чтобы предотвратить повреждение данных приложения. Даже если вы решили поймать OOM, вам следует попытаться собрать диагностическую информацию, такую ​​как журнал сбрасывания, stacktrace и т.д. Пожалуйста, не пытайтесь инициировать процедуру резервного копирования, так как вы не уверены, получит ли она возможность выполнить или нет.

Аналогия в реальном мире: вы путешествуете в самолете, и все двигатели терпят неудачу. Что бы вы сделали после обнаружения исключения AllEngineFailureException? Лучше всего захватить маску и подготовиться к крушению.

Когда в OOM, дамп!