Может ли JVM восстанавливаться из OutOfMemoryError без перезагрузки
-
Может ли JVM восстановить из OutOfMemoryError без перезагрузки, если у него есть шанс запустить GC, прежде чем появятся другие запросы на размещение объектов?
-
Различаются ли различные реализации JVM в этом аспекте?
Мой вопрос о восстановлении JVM, а не о программе пользователя, пытающейся восстановить, поймав ошибку. Другими словами, если на сервере приложений бросается OOME (jboss/websphere/..), я могу < иметь перезагрузить его? Или я могу позволить ему работать, если последующие запросы работают без проблем.
Ответы
Ответ 1
Это может сработать, но это, как правило, плохая идея. Нет гарантии, что ваше приложение будет успешным в восстановлении или что оно будет знать, если это не удалось. Например:
-
На самом деле может быть недостаточно памяти для выполнения запрошенных задач, даже после принятия шагов восстановления, таких как освобождение блока зарезервированной памяти. В этой ситуации ваше приложение может застревать в цикле, где он многократно появляется для восстановления, а затем снова исчерпывает память.
-
OOME может быть брошен на любой поток. Если нить приложения или библиотека не предназначены для его устранения, это может оставить некоторую долговременную структуру данных в неполном или несогласованном состоянии.
-
Если потоки умирают из-за OOME, приложение может потребоваться перезапустить их как часть восстановления OOME. По крайней мере, это усложняет приложение.
-
Предположим, что поток синхронизируется с другими потоками, используя механизм уведомления/ожидания или какой-то более высокий уровень. Если этот поток умирает из OOME, другие потоки могут быть оставлены в ожидании уведомлений (и т.д.), Которые никогда не приходят... например. Проектирование для этого может значительно усложнить приложение.
В целом, разработка, внедрение и тестирование приложения для восстановления от OOME могут быть трудными, особенно если приложение (или среда, в которой он работает, или любая из библиотек, которые он использует) является многопоточным. Лучше всего рассматривать OOME как фатальную ошибку.
См. также мой ответ на соответствующий вопрос:
EDIT - в ответ на следующий вопрос:
Другими словами, если OOME загружается на сервер приложений (jboss/websphere/..), я могу < перезагрузить его
Нет, вам не нужно перезагружать. Но это, вероятно, мудрый, особенно если у вас нет хорошего/автоматизированного способа проверки правильности работы службы.
JVM полностью восстановится. Но сервер приложений и само приложение могут или не могут восстановиться, в зависимости от того, насколько хорошо они предназначены для решения этой ситуации. (Мой опыт в том, что некоторые серверы приложений не предназначены для решения этой проблемы, и что разработка и внедрение сложного приложения для восстановления из OOMEs сложна, и тестирование его должным образом еще сложнее.)
РЕДАКТИРОВАТЬ 2
В ответ на этот комментарий:
"другие потоки могут быть оставлены в ожидании уведомлений (и т.д.), которые никогда не появятся" Действительно? Разве убитая нить не раскрутит свои стеки, не отпустив ресурсы, включая удерживаемые замки?
Да, действительно! Рассмотрим это:
Тема № 1 запускает это:
synchronized(lock) {
while (!someCondition) {
lock.wait();
}
}
// ...
Тема № 2 запускает это:
synchronized(lock) {
// do stuff
lock.notify();
}
Если в уведомлении ожидает Thread # 1, а Thread # 2 получает OOME в разделе // do something
, тогда Thread # 2 не вызовет вызов notify()
, и Thread # 1 может застрять навсегда для уведомления, которое никогда не произойдет. Конечно, Thread # 2 гарантированно освободит мьютекс объекта lock
... но этого недостаточно!
Если код, выполняемый потоком, не является безопасным исключением, это более общая проблема.
"Безопасное исключение" - это не термин, о котором я слышал (хотя я знаю, что вы имеете в виду). Программы Java обычно не предназначены для обеспечения устойчивости к неожиданным исключениям. В самом деле, в сценарии, подобном вышеизложенному, вероятно, где-то между жестким и невозможным сделать безопасным приложение.
Вам понадобится какой-то механизм, при котором отказ Thread # 1 (из-за OOME) превращается в уведомление об отключении связи между потоками в Thread # 2. Erlang делает это... но не Java. Причина, по которой они могут сделать это в Erlang, заключается в том, что процессы Erlang взаимодействуют с использованием строгих CSP-подобных примитивов; то есть нет совместного использования структур данных!
(Обратите внимание, что вы можете получить вышеупомянутую проблему практически для любого неожиданного исключения... не только исключений Error
. Существуют определенные типы кода Java, где попытка восстановления из неожиданного исключения может закончиться плохо.)
Ответ 2
JVM будет запускать GC, когда он находится на краю OutOfMemoryError
. Если GC вообще не помог, JVM будет бросать OOME.
Однако вы можете catch
и при необходимости принять альтернативный путь. Любые выделения внутри блока try
будут GC'ed.
Поскольку OOME является "просто" Error
, который вы могли бы просто catch
, я ожидал бы, что разные реализации JVM будут вести себя одинаково. Я могу, по крайней мере, подтвердить, что это верно для Sun JVM.
См. также:
Ответ 3
Я бы сказал, отчасти это зависит от того, что вызвало OutOfMemoryError. Если JVM действительно работает на низкой памяти, может быть хорошей идеей перезапустить его и с большим количеством памяти, если это возможно (или более эффективное приложение). Тем не менее, я видел много OOME, которые были вызваны распределением массивов 2 ГБ и т.д. В этом случае, если это что-то похожее на веб-приложение J2EE, последствия этой ошибки должны быть ограничены этим конкретным приложением, а перезапуск JVM не принесет никакой пользы.
Ответ 4
Может ли восстановить его? Возможно. Любая хорошо написанная JVM только собирается выбросить OOME после того, как она попробует все, что может, чтобы восстановить достаточно памяти, чтобы делать то, что вы говорите ей. Там очень хороший шанс, что это означает, что вы не можете восстановиться. Но...
Это зависит от многих вещей. Например, если сборщик мусора не является копирующим сборщиком, условие "из памяти" может фактически быть "нет куска, достаточно большого для распределения". Сам акт разматывания стека может содержать объекты, очищенные в более позднем раунде GC, которые оставляют открытые куски достаточно большими для ваших целей. В этой ситуации вы можете перезапустить. В результате, вероятно, стоит как минимум повторить попытку. Но...
Вероятно, вы не хотите полагаться на это. Если вы получаете OOME с какой-либо регулярностью, вам лучше просмотреть свой сервер и узнать, что происходит и почему. Возможно, вам нужно очистить свой код (вы можете просачиваться или создавать слишком много временных объектов). Возможно, вам нужно поднять потолок памяти при вызове JVM. Относитесь к OOME, даже если он восстанавливается, как признак того, что что-то плохое попало в вентилятор где-то в вашем коде и действует соответствующим образом. Может быть, ваш сервер не должен спускаться в NOWNOWNOWNOWNOW, но вам придется что-то исправить, прежде чем вы столкнетесь с более глубокими проблемами.
Ответ 5
Вы можете увеличить свои шансы на восстановление из этого сценария, хотя его не рекомендуется использовать. То, что вы делаете, это предварительное выделение некоторого фиксированного объема памяти при запуске, предназначенного для выполнения вашей работы по восстановлению, а когда вы поймаете OOM, обнулите эту предварительно выделенную ссылку, и у вас больше шансов иметь некоторую память для использования в вашем последовательности восстановления.
Я не знаю о различных реализациях JVM.
Ответ 6
Любой здравомыслящий JVM выкинет OutOfMemoryError только в том случае, если сборщик мусора не сможет сделать ничего. Однако, если вы поймаете OutOfMemoryError достаточно рано в кадре стека, может быть достаточно, чтобы причина сама по себе стала недостижимой и была собрана мусор (если проблема не в текущем потоке).
В общем случае фреймворки, которые запускают другой код, например серверы приложений, пытающийся продолжить работу перед OME, имеют смысл (до тех пор, пока он может разумно освободить сторонний код), но в противном случае в общем случае восстановление должно вероятно, состоят в том, чтобы ругаться и говорить пользователю, почему, а не пытаться продолжать, как будто ничего не произошло.
Чтобы ответить на ваш недавно обновленный вопрос: нет причин думать, что вам нужно закрыть сервер, если все работает хорошо. Мой опыт работы с JBoss заключается в том, что пока OME не влияет на развертывание, все работает нормально. Иногда JBoss исчерпывает пространство перменов, если вы делаете много горячего развертывания. Тогда действительно ситуация безнадежна, и немедленный перезапуск (который должен быть принудительно убит) неизбежен.
Конечно, каждый сервер приложений (и сценарий развертывания) будет меняться, и в каждом случае это действительно что-то извлеченное из опыта.