Ловить на Java.ленг.Вне памяти?


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

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

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

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

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

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

14 ответов:

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

есть несколько сценариев, где вы можете поймать OutOfMemoryError и по моему опыту (на Windows и Solaris JVMs), только очень редко это 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) {} везде или ерунда вроде этого.

вы можете восстановить из нее:

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

ничего не приходит на ум.

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

зависит от того, что вызвало пойдем. Если он объявлен вне try блок и это произошло шаг за шагом, то ваши шансы невелики. Ты мая хотите зарезервируйте место в памяти заранее:

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

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

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

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

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

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

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

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

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

EDIT

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

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

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

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

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

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

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

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

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

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

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

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

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

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 
bitmapOptions.inSampleSize = 1;
boolean imageSet = false;
while (!imageSet) {
  try {
    image = BitmapFactory.decodeFile(filePath, bitmapOptions);
    imageView.setImageBitmap(image); 
    imageSet = true;
  }
  catch (OutOfMemoryError e) {
    bitmapOptions.inSampleSize *= 2;
  }
}

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

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

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

полный список здесь

У меня есть приложение, которое должно восстанавливаться после сбоев OutOfMemoryError, и в однопоточных программах оно всегда работает, но иногда не работает в многопоточных программах. Приложение представляет собой автоматизированный инструмент тестирования Java, который выполняет сгенерированные тестовые последовательности до максимально возможной глубины на тестовых классах. Теперь пользовательский интерфейс должен быть стабильным, но тестовый движок может работать без памяти при росте дерева тестовых случаев. Я справляюсь с этим следующим видом идиомы кода в тесте двигатель:

boolean isOutOfMemory = false;  // flag used for reporting
try {
   SomeType largeVar;
   // Main loop that allocates more and more to largeVar
   // may terminate OK, or raise OutOfMemoryError
}
catch (OutOfMemoryError ex) {
   // largeVar is now out of scope, so is garbage
   System.gc();                // clean up largeVar data
   isOutOfMemory = true;       // flag available for use
}
// program tests flag to report recovery

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

например, у меня был OOME происходят в то время как кадры анимированного GIF в моем пользовательском интерфейсе были циклически проприетарный поток, который создается за кулисами класса Swing, который находится вне моего контроля. Я думал, что я выделил все необходимые ресурсы заранее, но ясно, что аниматор выделяет память каждый раз, когда он выбирает следующее изображение. Если у кого-то есть идея о том, как обращаться с умами, поднятыми в любой нить, я хотел бы услышать.

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

для вопроса 2 я уже вижу решение, которое я бы предложил, BalusC.

  1. есть ли реальные сценарии слов при ловле java.ленг.OutOfMemoryError может быть хорошей идеей?

Я думаю, что я только что наткнулся на хороший пример. Когда приложение awt отправляет сообщения, uncatched OutOfMemoryError отображается на stderr, и обработка текущего сообщения останавливается. Но приложение продолжает работать! Пользователь может все еще другие команды не подозревая о серьезных проблемах, происходящих за сценой. Особенно когда он не может или не соблюдает стандартную ошибку. Таким образом, улавливание исключения oom и предоставление (или, по крайней мере, предложение) перезапуска приложения является чем-то желательным.

OOME может быть пойман, но будет в целом бесполезным, в зависимости от того, сможет ли JVM собирать мусор некоторые объекты, когда будет достигнут улов, и сколько памяти кучи осталось к тому времени.

пример: в моей JVM эта программа выполняется до завершения:

import java.util.LinkedList;
import java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
        }
        System.out.println("Test finished");
    }  
}

однако, просто добавив одну строку на улове покажет вам, о чем я говорю:

import java.util.LinkedList;
import java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
            System.out.println("size:" +ll.size());
        }
        System.out.println("Test finished");
    }
}

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

однако, если код устроен так, как список ll используется после того, как OOME был пойман, JVM не может его собрать. Это происходит во втором фрагменте. Ум, вызванный новым длинным создание, ловится, но вскоре мы создаем новый объект (строка в System.out,println line), и куча почти полна, поэтому новый OOME выбрасывается. Это худший сценарий: мы пытались создать новый объект, мы потерпели неудачу, мы поймали OOME, да, но теперь первая инструкция, требующая новой памяти кучи (например: создание нового объекта), бросит новый OOME. Подумайте об этом, что еще мы можем сделать в этот момент с таким небольшим количеством памяти осталось?. Наверное, просто вышел. Следовательно, бесполезный.

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

Я использую Windows для архитектуры x86 32 бита для JVM (JRE6). Память по умолчанию для каждого приложения Java составляет 64 МБ.

У меня просто есть сценарий, где ловить OutOfMemoryError, кажется, имеет смысл и, кажется, работает.

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

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

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

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

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

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

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

  1. зависит от того, как вы определяете "хороший". Мы делаем это в нашем багги веб-приложения, и он действительно работает большую часть времени (к счастью, теперь OutOfMemory не происходит в связи с исправлением). Однако, даже если вы поймаете его, он все равно может сломать какой-то важный код: если у вас есть несколько потоков, выделение памяти может завершиться неудачей в любом из них. Таким образом, в зависимости от вашего приложения есть еще 10--90% шанс того, что он будет необратимо нарушен.
  2. насколько я поймите, тяжелая размотка стека на пути приведет к недействительности стольких ссылок и, таким образом, освободит столько памяти, что вам не стоит беспокоиться об этом.

EDIT:я предлагаю вам попробовать его. Скажем, написать программу, которая рекурсивно вызывает функцию, которая постепенно выделяет больше памяти. Лови OutOfMemoryError и посмотреть, если вы можете конструктивно дальше от этой точки. По моему опыту, вы сможете, хотя в моем случае это произошло под WebLogic server, так что, возможно, было тут замешана какая-то черная магия.