Подтвердить что ты не робот

Java - закрытие ошибки при отсутствии памяти

Я слышал очень противоречивые вещи о том, как лучше всего справиться с этим, и я придерживаюсь следующей дилеммы:

  • OOME приносит поток, но не все приложение
  • и мне нужно сбить все приложение, но не могу, потому что у потока нет оставшейся памяти

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

4b9b3361

Ответ 1

OutOfMemoryError как всякая другая ошибка. Если он ускользнет от Thread.run(), это приведет к смерти нити. Больше ничего. Кроме того, когда нить умирает, она больше не является GC-корнем, поэтому все ссылки, хранящиеся только в этом потоке, имеют право на сбор мусора. Это означает, что JVM, скорее всего, восстановится с OOME.

Если вы хотите убить свою JVM независимо от того, потому что вы подозреваете, что она может находиться в несогласованном состоянии, добавьте ее в свои параметры java:

-XX:OnOutOfMemoryError="kill -9 %p"

%p - это текущий заполнитель PID для процесса Java. Остальное объясняется самостоятельно.

Конечно, вы также можете попробовать OutOfMemoryError и обработать его каким-то образом. Но это сложно.

Ответ 2

В версии 8u92 теперь есть опция JVM в Oracle JDK, чтобы сделать JVM-выход, когда возникает OutOfMemoryError:

Из примечаний к выпуску:

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

Ответ 3

В Java версии 8u92 аргументы VM

  • -XX:+ExitOnOutOfMemoryError
  • -XX:+CrashOnOutOfMemoryError

см. примечания к выпуску.

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

CrashOnOutOfMemoryError
Если этот параметр включен, ошибка при отсутствии памяти, JVM аварийно завершает работу и создает текст и двоичные файлы сбоя.

Запрос на улучшение: JDK-8138745 (неправильное имя параметра, JDK-8154713, ExitOnOutOfMemoryError вместо ExitOnOutOfMemory)

Ответ 4

Если вы хотите сбить свою программу, ознакомьтесь с опцией -XX:OnOutOfMemoryError="<cmd args>;<cmd args>" (documented here) в командной строке. Просто укажите его на kill script для вашего приложения.

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

Ответ 5

Вы можете принудительно завершить свою программу несколькими способами, как только ошибка закроется. Как и другие, вы можете поймать ошибку и сделать System.exit после этого, если это необходимо. Но я предлагаю вам также использовать -XX: + HeapDumpOnOutOfMemoryError, таким образом, JVM создаст файл дампа памяти с содержимым вашего приложения после создания события. Вы будете использовать профили, я рекомендую вам Eclipse MAT для исследования изображения. Таким образом, вы найдете довольно быстро, в чем причина проблемы, и отреагируйте должным образом. Если вы не используете Eclipse, вы можете использовать Eclipse MAT в качестве отдельного продукта, см. http://wiki.eclipse.org/index.php/MemoryAnalyzer.

Ответ 6

Вообще говоря, вы никогда не должны писать блок catch, который ловит java.lang.Error или любой из его подклассов, включая OutOfMemoryError. Единственное исключение - если вы используете стороннюю библиотеку, которая бросает пользовательский подкласс Error, когда они должны иметь подклассы RuntimeException. Это действительно просто работа для ошибок в их коде, хотя.

Из JavaDoc для java.lang.Error:

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

Если у вас возникли проблемы с тем, что приложение продолжает работать даже после того, как один из потоков умирает из-за OOME, у вас есть несколько вариантов.

Во-первых, вы можете проверить, можно ли пометить оставшиеся потоки как потоки демона. Если есть когда-либо точка, когда в JVM остаются только потоки демона, она будет запускать все крючки остановки и завершаться как можно более аккуратно. Для этого вам нужно вызвать setDaemon(true) объекта потока до его запуска. Если потоки фактически созданы каркасом или каким-либо другим кодом, вам может потребоваться использовать другое средство для установки этого флага.

Другой вариант - назначить обработчик неперехваченных исключений для рассматриваемых потоков и вызвать либо System.exit(), либо, если необходимо, Runtime.getRuntime().halt(). Вызов остановки очень опасен, поскольку крючки остановки даже не будут запускаться, но в некоторых ситуациях остановка может работать там, где System.exit потерпел бы неудачу, если OOME уже был выброшен.

Ответ 7

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

public class ExitProcessOnUncaughtException implements UncaughtExceptionHandler  
{
    static public void register()
    {
        Thread.setDefaultUncaughtExceptionHandler(new ExitProcessOnUncaughtException());
    }

    private ExitProcessOnUncaughtException() {}


    @Override
    public void uncaughtException(Thread t, Throwable e) 
    {
        try {
            StringWriter writer = new StringWriter();
            e.printStackTrace(new PrintWriter(writer));
            System.out.println("Uncaught exception caught"+ " in thread: "+t);
            System.out.flush();
            System.out.println();
            System.err.println(writer.getBuffer().toString());
            System.err.flush();
            printFullCoreDump();
        } finally {
            Runtime.getRuntime().halt(1);
        }
    }

    public static void printFullCoreDump()
    {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("\n"+
            sdf.format(System.currentTimeMillis())+"\n"+
            "All Stack Trace:\n"+
            getAllStackTraces()+
            "\nHeap\n"+
            getHeapInfo()+
            "\n");
    }

    public static String getAllStackTraces()
    {
        String ret="";
        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();

        for (Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet())
            ret+=getThreadInfo(entry.getKey(),entry.getValue())+"\n";
        return ret;
    }

    public static String getHeapInfo()
    {
        String ret="";
        List<MemoryPoolMXBean> memBeans = ManagementFactory.getMemoryPoolMXBeans();               
        for (MemoryPoolMXBean mpool : memBeans) {
            MemoryUsage usage = mpool.getUsage();

            String name = mpool.getName();      
            long used = usage.getUsed();
            long max = usage.getMax();
            int pctUsed = (int) (used * 100 / max);
            ret+=" "+name+" total: "+(max/1000)+"K, "+pctUsed+"% used\n";
        }
        return ret;
    }

    public static String getThreadInfo(Thread thread, StackTraceElement[] stack)
    {
        String ret="";
        ret+="\n\""+thread.getName()+"\"";
        if (thread.isDaemon())
            ret+=" daemon";
        ret+=
                " prio="+thread.getPriority()+
                " tid="+String.format("0x%08x", thread.getId());
        if (stack.length>0)
            ret+=" in "+stack[0].getClassName()+"."+stack[0].getMethodName()+"()";
        ret+="\n   java.lang.Thread.State: "+thread.getState()+"\n";
        ret+=getStackTrace(stack);
        return ret;
    }

    public static String getStackTrace(StackTraceElement[] stack)
    {
        String ret="";
        for (StackTraceElement element : stack)
            ret+="\tat "+element+"\n";
        return ret;
    }
}

Ответ 8

Вы можете окружить код потока с помощью try catch для OOME и выполнить ручную очистку, если произойдет такое событие. Трюк заключается в том, чтобы сделать вашу функцию потока только попыткой поймать другую функцию. При ошибке памяти он должен освободить некоторое пространство в стеке, что позволит вам быстро удалить. Это должно работать, если вы выполняете запрос на сборку мусора на некоторых ресурсах сразу после ловли и/или устанавливаете флаг умирания, чтобы сообщить другим потокам прекратить работу.

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

Ответ 9

Вы не должны обращаться с OOM каким-либо образом. Вы должны это исправить. Для этого, естественно, вы должны найти основную причину утечки памяти: какие объекты протекают и почему. К сожалению, как можно видеть в этой серии блога, это не так просто. Но вы можете попробовать Plumbr. Он должен работать лучше других:)