Ловля java.lang. OutOfMemoryError?

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

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

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

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

Так, мой вопрос:

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

97
задан Dov Vachman 3 July 2019 в 05:40
поделиться

8 ответов

Я согласен и не согласен с большинством приведенных здесь ответов.

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

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

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

Когда выдается ошибка , куча содержит такое же количество выделенных объектов, что и до неудачного выделения, и теперь пора отбросить ссылки на объекты времени выполнения, чтобы освободить еще больше памяти, которая может потребоваться для очистки. В этих случаях можно даже продолжить, но это определенно будет плохой идеей, поскольку вы никогда не можете быть на 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

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

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

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

Определенно существуют сценарии, в которых обнаружение OOME имеет смысл. IDEA улавливает их и открывает диалоговое окно, позволяющее изменить настройки загрузочной памяти (а затем закрывается, когда вы закончите). Сервер приложений может их поймать и сообщить. Ключ к этому - сделать это на высоком уровне отправки, чтобы у вас была разумная вероятность высвободить кучу ресурсов в точке, где вы перехватываете исключение.

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

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

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

Да, реальный вопрос: «что вы собираетесь делать в обработчике исключений?» Практически для всего полезного вы выделяете больше памяти. Если вы хотите провести диагностику при возникновении OutOfMemoryError, вы можете использовать ловушку -XX: OnOutOfMemoryError = , предоставляемую виртуальной машиной HotSpot. Он выполнит ваши команды при возникновении OutOfMemoryError, и вы сможете сделать что-нибудь полезное вне кучи Java. Вы действительно хотите, чтобы приложение не исчерпало память, поэтому первый шаг - выяснить, почему это происходит. Затем вы можете при необходимости увеличить размер кучи MaxPermSize. Вот еще несколько полезных ловушек HotSpot:

-XX:+PrintCommandLineFlags
-XX:+PrintConcurrentLocks
-XX:+PrintClassHistogram

См. Полный список здесь

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

Единственная причина, по которой я могу думать о том, почему перехват ошибок OOM может заключаться в том, что у вас есть массивные структуры данных, которые вы больше не используете, и можете установить значение null и освободите немного памяти. Но (1) это означает, что вы тратите память и вам следует исправить свой код, а не просто хромать после OOME, и (2) даже если вы его поймали, что бы вы сделали? OOM может произойти в любой момент, потенциально оставив все наполовину готовым.

3
ответ дан 24 November 2019 в 05:27
поделиться
  1. Зависит от того, как вы определяете «хорошо». Мы делаем это в нашем веб-приложении с ошибками, и оно работает большую часть времени (к счастью, теперь OutOfMemory не происходит из-за несвязанного исправления). Однако, даже если вы его поймаете, он все равно может сломать какой-то важный код: если у вас несколько потоков, выделение памяти может не удастся в любом из них. Таким образом, в зависимости от вашего приложения вероятность необратимого взлома составляет 10--90%.
  2. Насколько я понимаю, тяжелая раскрутка стека по пути сделает недействительными так много ссылок и, таким образом, освободит столько памяти, что вам не стоит беспокоиться об этом.

РЕДАКТИРОВАТЬ: Предлагаю вам попробовать. Скажем, напишите программу, которая рекурсивно вызывает функцию, которая постепенно выделяет больше памяти. Перехватите OutOfMemoryError и посмотрите, сможете ли вы осмысленно продолжить с этого момента. По моему опыту, вы сможете это сделать, хотя в моем случае это произошло на сервере WebLogic, так что здесь могла быть какая-то черная магия.

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

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

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

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

Необходимо выяснить, почему у вас заканчивается память (использовать профилировщик, такой как в NetBeans), и убедиться, что у вас нет утечек памяти. Если у вас нет утечек памяти, увеличьте объем памяти, который вы выделяете виртуальной машине.

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

В общем, пытаться поймать и восстановить OOM - плохая идея.

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

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

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

(Предполагается, что JVM умирает . Например, OOM в потоке сервлета Tomcat не уничтожают JVM, и это приводит к тому, что Tomcat переходит в кататоническое состояние, когда он не реагирует на любые запросы ... даже не запросы на перезапуск.)

РЕДАКТИРОВАТЬ

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

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

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

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

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

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

{ {1}}
15
ответ дан 24 November 2019 в 05:27
поделиться

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

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: есть ли какие-либо сценарии реального слова при отлове java.lang.OutOfMemoryError может быть хорошей идеей ?

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

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

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

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

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

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

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

29
ответ дан 24 November 2019 в 05:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: