Java - 关闭内存不足错误

时间:2012-08-23 16:41:07

标签: java out-of-memory

我听说过如何最好地解决这个问题的矛盾,我陷入了以下困境:

  • 一个OOME带来了一个线程,但不是整个应用程序
  • 我需要关闭整个应用程序但不能因为线程没有任何内存

我一直都知道最好的做法是让他们离开以便JVM可以死掉,因为JVM在那时处于不一致的状态,但这似乎不起作用。

10 个答案:

答案 0 :(得分:42)

OutOfMemoryError就像任何其他错误一样。如果它从Thread.run()中逃脱,将导致线程死亡。而已。此外,当线程死亡时,它不再是GC根,因此仅由此线程保留的所有引用都有资格进行垃圾回收。这意味着JVM很可能从OOME恢复。

如果你想要杀死你的JVM,不管是因为你怀疑它可能处于不一致的状态,请将其添加到java选项中:

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

%p是当前的Java进程PID占位符。其余的是自我解释。

当然你也可以尝试捕捉OutOfMemoryError并以某种方式处理它。但这很棘手。

答案 1 :(得分:29)

对于版本8u92,Oracle JDK中现在有一个JVM选项,可以在发生OutOfMemoryError时退出JVM:

来自release notes

  

ExitOnOutOfMemoryError - 启用此选项时,JVM会在第一次出现内存不足错误时退出。如果您更喜欢重新启动JVM实例而不是处理内存不足错误,则可以使用它。

答案 2 :(得分:26)

在Java版本8u92中,VM参数

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

已添加,请参阅release notes

  

<强> ExitOnOutOfMemoryError
  启用此选项后,JVM将退出   第一次出现内存不足错误。如果你可以使用它   更喜欢重新启动JVM的实例而不是处理掉   记忆错误。

     

<强> CrashOnOutOfMemoryError
  如果启用此选项,则启用此选项   发生内存不足错误,JVM崩溃并生成文本和   二进制崩溃文件。

增强请求:JDK-8138745(参数命名错误JDK-8154713ExitOnOutOfMemoryError而不是ExitOnOutOfMemory

答案 3 :(得分:5)

一旦错误发生,您可以强制程序以多种方式终止。像其他人建议的那样,如果需要,您可以捕获错误并在此之后执行System.exit。 但我建议您也使用-XX:+ HeapDumpOnOutOfMemoryError,这样JVM将在生成事件后使用应用程序的内容创建内存转储文件。您将使用配置文件,我建议您使用Eclipse MAT来调查图像。通过这种方式,您可以快速找到问题的原因,并做出正确的反应。如果您不使用Eclipse,则可以将Eclipse MAT用作独立产品,请参阅:http://wiki.eclipse.org/index.php/MemoryAnalyzer

答案 4 :(得分:4)

如果要关闭程序,请查看命令行上的-XX:OnOutOfMemoryError="<cmd args>;<cmd args>"documented here)选项。只需将其指向应用程序的kill脚本即可。

一般情况下,如果不重新启动应用程序,我就没有任何运气可以优雅地处理此错误。总是有某种角落滑落,所以我个人建议确实停止你的申请,但调查问题的根源。

答案 5 :(得分:1)

一般来说,您永远不应该编写捕获java.lang.Error或其任何子类(包括OutOfMemoryError)的catch块。唯一的例外是,如果您使用第三方库,当它们应该具有子类Error时会抛出RuntimeException的自定义子类。这实际上只是解决了代码中的错误。

来自java.lang.Error的{​​{3}}:

  

错误是Throwable的子类,表示严重问题   合理的申请不应该试图抓住。

如果您的应用程序出现问题,即使其中一个线程因为OOME而死亡,您仍然可以继续运行。

首先,您可能需要检查是否可以将剩余的线程标记为守护程序线程。如果只有守护程序线程保留在JVM中,它将运行所有关闭挂钩并尽可能有序地终止。为此,您需要在线程对象启动之前调用setDaemon(true)。如果线程实际上是由框架或其他代码创建的,则可能必须使用不同的方法来设置该标志。

另一个选项是为有问题的线程分配一个未捕获的异常处理程序,并调用System.exit()或绝对必要的Runtime.getRuntime().halt()。调用暂停是非常危险的,因为关闭挂钩甚至不会尝试运行,但在某些情况下,如果已经抛出OOME,则停止可能会在System.exit失败的情况下工作。

答案 6 :(得分:1)

我建议在应用程序中处理所有未捕获的异常,以确保它在终止之前尝试为您提供最佳数据。 然后有一个外部脚本,当它崩溃时重启你的进程。

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;
    }
}

答案 7 :(得分:0)

您可以使用针对OOME的try catch包围线程代码,并在发生此类事件时进行一些手动清理。一个技巧是让你的线程函数只是尝试捕获另一个函数。在内存错误时,它应该释放堆栈上的一些空间,允许您进行一些快速删除。如果你在捕获后立即对某些资源执行垃圾收集请求和/或设置一个死标志以告诉其他线程退出,这应该有效。

一旦与OOME的线程死亡并且你对它的元素进行了一些收集,你应该有足够的可用空间让其他线程以有序的方式退出。这是一个更优雅的戒烟,有机会在死前记录问题。

答案 8 :(得分:0)

由于JVM选项

-XX:+ExitOnOutOfMemoryError

-XX:+CrashOnOutOfMemoryError

-XX:OnOutOfMemoryError=...

如果由于线程耗尽而导致OutOfMemoryError错误,则

不起作用(请参见the corresponding JDK bug report),可能值得尝试使用工具jkill。如果内存或可用线程已用完,它将通过JVMTI注册并退出VM。

在我的测试中,它按预期方式工作(以及我希望JVM选项如何工作)。

答案 9 :(得分:-2)

您不应该以任何方式处理 OOM。您必须修复它。为此,当然,您必须找到内存泄漏的根本原因:哪些对象泄漏以及原因。不幸的是,正如this blog series中所见,这并不容易。但你可以尝试Plumbr。它应该比其他人更好:)