Когда можно поймать исключение OutOfMemoryException и как с ним справиться? - PullRequest
36 голосов
/ 22 января 2010

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

Мои плюсы для обработки:

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

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

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

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

Ответы [ 10 ]

35 голосов
/ 22 января 2010

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

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

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

21 голосов
/ 22 января 2010

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

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

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

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

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

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

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

18 голосов
/ 24 января 2010

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

13 голосов
/ 22 января 2010

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

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

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

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

10 голосов
/ 22 января 2010

Предложите комментарий Кристофера Брамма в «Руководстве по разработке структуры», с.238 (7.3.7 OutOfMemoryException):

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

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

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

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

8 голосов
/ 22 января 2010

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

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

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

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

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

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

Так какое это имеет отношение к OutOfMemoryException конкретно?

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

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

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

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

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

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

6 голосов
/ 22 января 2010

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

2 голосов
/ 22 января 2010

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

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

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

1 голос
/ 22 января 2010

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

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

0 голосов
/ 25 января 2010

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

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

В OOM, сбрасывать !!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...