Вопрос-ответ

Catching java.lang.OutOfMemoryError?

Ловит java.lang.OutOfMemoryError?

Вдокументации для java.lang.Error говорится:


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


Но поскольку java.lang.Error это подкласс java.lang.Throwable, я могу поймать этот тип Throwable .

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

Итак, мой вопрос:


  1. Существуют ли какие-либо реальные сценарии, когда перехват java.lang.OutOfMemoryError может быть хорошей идеей?

  2. Если мы решим перехватить java.lang.OutOfMemoryError, как мы можем убедиться, что обработчик catch не выделяет память сам по себе (какие-либо инструменты или рекомендации)?

Переведено автоматически
Ответ 1

Существует ряд сценариев, в которых вы, возможно, захотите перехватить OutOfMemoryError и, по моему опыту (в Windows и Solaris JVM), только очень редко это OutOfMemoryError похоронный звон по JVM.

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

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

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

Демонстрация того, что OutOfMemoryError не означает, что JVM не хватает памяти в блоке catch:

private static final int MEGABYTE = (1024*1024);
public static void runOutOfMemory() {
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
for (int i=1; i <= 100; i++) {
try {
byte[] bytes = new byte[MEGABYTE*500];
} catch (Exception e) {
e.printStackTrace();
} catch (OutOfMemoryError e) {
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
long maxMemory = heapUsage.getMax() / MEGABYTE;
long usedMemory = heapUsage.getUsed() / MEGABYTE;
System.out.println(i+ " : Memory Use :" + usedMemory + "M/" +maxMemory+"M");
}
}
}

Вывод этого кода:

1 : Memory Use :0M/247M
..
..
..
98 : Memory Use :0M/247M
99 : Memory Use :0M/247M
100 : Memory Use :0M/247M

Если я запускаю что-то критическое, я обычно перехватываю Error, регистрирую это в syserr, затем регистрирую это с помощью выбранной мной платформы ведения журнала, затем приступаю к освобождению ресурсов и завершаю работу чистым способом. Что самое худшее, что может случиться? JVM все равно умирает (или уже мертва), и при перехватывании Error есть хотя бы шанс на очистку.

Предостережение заключается в том, что вы должны нацеливаться на отлов этих типов ошибок только в местах, где возможна очистка. Не покрывайте catch(Throwable t) {} везде или подобную ерунду.

Ответ 2

Вы можете восстановиться после этого:

package com.stackoverflow.q2679330;

public class Test {

public static void main(String... args) {
int size = Integer.MAX_VALUE;
int factor = 10;

while (true) {
try {
System.out.println("Trying to allocate " + size + " bytes");
byte[] bytes = new byte[size];
System.out.println("Succeed!");
break;
} catch (OutOfMemoryError e) {
System.out.println("OOME .. Trying again with 10x less");
size /= factor;
}
}
}

}

Но имеет ли это смысл? Что еще вы хотели бы сделать? Зачем вам изначально выделять так много памяти? Меньшее количество памяти тоже нормально? Почему вы все равно этим уже не пользуетесь? Или, если это невозможно, почему бы просто не предоставить JVM больше памяти с самого начала?

Вернемся к вашим вопросам:


1: существуют ли какие-либо реальные сценарии word при перехватывании java.lang.OutOfMemoryError может быть хорошей идеей?


Ничего не приходит в голову.


2: если мы перехватываем java.lang.OutOfMemoryError, как мы можем быть уверены, что обработчик catch не выделяет память сам по себе (какими-либо инструментами или лучшими практиками)?


Зависит от того, что вызвало OOME. Если это объявлено вне try блока и это происходило поэтапно, то ваши шансы невелики. Вы возможно захотите заранее зарезервировать немного места в памяти:

private static byte[] reserve = new byte[1024 * 1024]; // Reserves 1MB.

а затем установите его равным нулю во время OOME:

} catch (OutOfMemoryException e) {
reserve = new byte[0];
// Ha! 1MB free!
}

Конечно, в этом нет никакого смысла ;) Просто предоставьте JVM достаточно памяти, как того требует ваше приложение. При необходимости запустите профилировщик.

Ответ 3

В общем, пытаться перехватывать и восстанавливать из ООМ - плохая идея.


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



  2. Даже если вы успешно восстановитесь, ваша JVM все еще может страдать от нехватки кучи, и в результате ваше приложение будет работать плачевно.



Лучшее, что можно сделать с OOME, - это позволить JVM умереть.

(Это предполагает, что JVM действительно умирает. Например, OOM в потоке сервлета Tomcat не уничтожает JVM. Это, как известно, приводит к тому, что Tomcat переходит в кататоническое состояние, когда он вообще не отвечает ни на какие запросы. Даже не запрашивает порт управления, сообщающий ему перезапустить себя!)

Редактировать

Я не говорю, что ловить OOM вообще плохая идея. Проблемы возникают, когда вы затем пытаетесь восстановиться из OOME, намеренно или по недосмотру. Всякий раз, когда вы перехватываете ООМ (напрямую, или как подтип ошибки, или как выбрасываемый), вы должны либо перенаправить его, либо организовать завершение работы приложения / JVM.

В сторону: это говорит о том, что для максимальной надежности перед лицом ООМ приложение должно использовать Thread.setDefaultUncaughtExceptionHandler() чтобы установить обработчик, который приведет к завершению работы приложения в случае ООМ, независимо от того, в каком потоке выполняется ООМ. Мне было бы интересно узнать мнения по этому поводу...

Единственный другой сценарий - это когда вы наверняка знаете, что ООМ не привел к какому-либо сопутствующему ущербу; т.е. вы знаете:


  • что конкретно вызвало OOME,

  • что приложение делало в то время, и что можно просто отбросить это вычисление, и

  • что (примерно) одновременный OOME не мог произойти в другом потоке.

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

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

Ответ 4

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

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

java