Когда это в порядке для ловли OutOfMemoryException и как обработать его?

Вчера я принял участие в обсуждении ТАК посвященного OutOfMemoryException и за и против обработки его (попытка C# {} выгода {}).

Мои профессионалы для обработки его были:

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

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

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

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

36
задан Community 23 May 2017 в 11:46
поделиться

10 ответов

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

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

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

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

На вашу правку ...

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

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

21
ответ дан 27 November 2019 в 05:16
поделиться

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

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

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

2
ответ дан 27 November 2019 в 05:16
поделиться

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

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

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

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

13
ответ дан 27 November 2019 в 05:16
поделиться

Предложить комментариев Кристофера Бревем в разделе «Рекомендация рамочной конструкции» P.238 (7.3.7 OutofMemoryException):

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

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

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

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

10
ответ дан 27 November 2019 в 05:16
поделиться

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

6
ответ дан 27 November 2019 в 05:16
поделиться

Некоторые комментаторы отметили, что существуют ситуации, когда OOM может быть немедленным результатом попытки выделить большой Количество байтов (графическое приложение, выделение большого массива и т. Д.). Обратите внимание, что для этой цели вы можете использовать класс MemoryFailpoint , что повышает InsificationMemoryException (сам, полученный из OutofMemoryException). Это может быть благополучно , поскольку он поднят до Фактическая попытка выделить память. Однако это может действительно снизить вероятность оома, никогда не полностью предотвращающуюся.

18
ответ дан 27 November 2019 в 05:16
поделиться

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

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

1
ответ дан 27 November 2019 в 05:16
поделиться

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

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

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

-2
ответ дан 27 November 2019 в 05:16
поделиться

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

Я могу ошибаться, но мне кажется, что это сообщение говорит о Большой Проблеме. Правильное исправление - выяснить, почему вы расплющили хотя бы одну память, и обратиться к ней (например, есть ли у вас утечка? не могли бы вы переключиться на потоковое API?). Даже переключение на x64 здесь не является волшебной палочкой; массивы (а значит и списки) все равно ограничены по размеру; а увеличенный размер ссылки означает, что вы можете исправить численно меньшее количество ссылок в 2GB объектной крышке.

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

35
ответ дан 27 November 2019 в 05:16
поделиться

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

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

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

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

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

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

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

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

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

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

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

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

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

8
ответ дан 27 November 2019 в 05:16
поделиться