捕获 java.lang.OutOfMemoryError?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2679330/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 10:44:06  来源:igfitidea点击:

Catching java.lang.OutOfMemoryError?

javatry-catchout-of-memory

提问by Denis Bazhenov

Documentationfor java.lang.Errorsays:

文档java.lang.Error说:

An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch

一个 Error 是 Throwable 的一个子类,它表明一个合理的应用程序不应该试图捕捉的严重问题

But as java.lang.Erroris a subclass of java.lang.Throwable, I can catch this type of Throwable.

但是作为java.lang.Error的子类java.lang.Throwable,我可以捕获这种类型的 Throwable。

I understand why it's not a good idea to catch this sort of exception. As far as I understand, if we decide to catch it, the catch handler should not allocate any memory by itself. Otherwise OutOfMemoryErrorwill be thrown again.

我明白为什么捕捉这种异常不是一个好主意。据我了解,如果我们决定捕获它,则捕获处理程序不应自行分配任何内存。否则OutOfMemoryError会再次抛出。

So, my question is:

所以,我的问题是:

  1. Are there any real world scenarios when catching java.lang.OutOfMemoryErrormight be a good idea?
  2. If we decide to catch java.lang.OutOfMemoryError, how can we make sure the catch handler doesn't allocate any memory by itself (any tools or best practices)?
  1. 当捕捉java.lang.OutOfMemoryError可能是一个好主意时,是否有任何现实世界的场景?
  2. 如果我们决定 catch java.lang.OutOfMemoryError,我们如何确保 catch 处理程序不会自行分配任何内存(任何工具或最佳实践)?

采纳答案by Chris

I agree and disagree with most the responses here.

我同意和不同意这里的大多数回答。

There are a number of scenarios where you may wish to catch an OutOfMemoryErrorand in my experience (on Windows and Solaris JVMs), only very infrequently is OutOfMemoryErrorthe death-knell to a JVM.

OutOfMemoryError根据我的经验(在 Windows 和 Solaris JVM 上),您可能希望了解许多场景,但在极少数情况下是OutOfMemoryErrorJVM 的丧钟。

There is only one good reason to catch an OutOfMemoryErrorand that is to close down gracefully, cleanly releasing resources and logging the reason for the failure best you can (if it is still possible to do so).

只有一个很好的理由可以捕获OutOfMemoryError,那就是优雅地关闭,干净地释放资源并尽可能地记录失败的原因(如果仍然可以这样做)。

In general, the OutOfMemoryErroroccurs due to a block memory allocation that cannot be satisfied with the remaining resources of the heap.

通常,OutOfMemoryError发生这种情况是由于块内存分配无法满足堆的剩余资源。

When the Erroris thrown the heap contains the same amount of allocated objects as before the unsuccessful allocation and now is the time to drop references to run-time objects to free even more memory that may be required for cleanup. In these cases, it may even be possible to continue but that would definitely be a bad idea as you can never be 100% certain that the JVM is in a reparable state.

Error被抛出时,堆包含与分配不成功之前相同数量的已分配对象,现在是时候删除对运行时对象的引用以释放更多可能需要清理的内存。在这些情况下,甚至有可能继续,但这绝对是一个坏主意,因为您永远无法 100% 确定 JVM 处于可修复状态。

Demonstration that OutOfMemoryErrordoes not mean that the JVM is out of memory in the catch block:

演示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");
        }
    }
}

Output of this code:

此代码的输出:

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

If running something critical, I usually catch the Error, log it to syserr, then log it using my logging framework of choice, then proceed to release resources and close down in a clean fashion. What's the worst that can happen? The JVM is dying (or already dead) anyway and by catching the Errorthere is at least a chance of cleanup.

如果运行一些关键的东西,我通常会捕获Error,将其记录到 syserr,然后使用我选择的日志记录框架记录它,然后继续释放资源并以干净的方式关闭。可能发生的最坏情况是什么?无论如何,JVM 正在消亡(或已经死了)并且通过捕获Error至少有清理的机会。

The caveat is that you have to target the catching of these types of errors only in places where cleanup is possible. Don't blanket catch(Throwable t) {}everywhere or nonsense like that.

需要注意的是,您必须仅在可以清理的地方捕获这些类型的错误。不要catch(Throwable t) {}到处盖毯子或那样胡说八道。

回答by doublep

  1. Depends on how you define "good". We do that in our buggy web application and it does work most of the time(thankfully, now OutOfMemorydoesn't happen due to an unrelated fix). However, even if you catch it, it still might have broken some important code: if you have several threads, memory allocation can fail in any of them. So, depending on your application there is still 10--90% chance of it being irreversibly broken.
  2. As far as I understand, heavy stack unwinding on the way will invalidate so many references and thus free so much memory you shouldn't care about that.
  1. 看你怎么定义“好”了。我们在有问题的 Web 应用程序中这样做,并且它在大多数时间都可以工作(谢天谢地,OutOfMemory由于无关的修复,现在不会发生)。然而,即使你抓住了它,它仍然可能破坏了一些重要的代码:如果你有多个线程,内存分配可能会在其中任何一个中失败。因此,根据您的应用程序,它仍有 10--90% 的机会不可逆转地损坏。
  2. 据我了解,在此过程中大量堆栈展开将使如此多的引用无效,从而释放大量您不应该关心的内存。

EDIT: I suggest you try it out. Say, write a program that recursively calls a function that allocates progressively more memory. Catch OutOfMemoryErrorand see if you can meaningfully continue from that point. According to my experience, you will be able to, though in my case it happened under WebLogic server, so there might have been some black magic involved.

编辑:我建议你尝试一下。比方说,编写一个程序,递归调用一个逐渐分配更多内存的函数。抓住OutOfMemoryError并看看您是否可以从这一点有意义地继续。根据我的经验,您将能够,尽管在我的情况下它发生在 WebLogic 服务器下,因此可能涉及一些黑魔法。

回答by TofuBeer

You can catch anything under Throwable, generally speaking you should only catch subclasses of Exception excluding RuntimeException (though a large portion of developers also catch RuntimeException... but that was never the intent of the language designers).

你可以在 Throwable 下捕获任何东西,一般来说你应该只捕获 Exception 的子类,不包括 RuntimeException(尽管很大一部分开发人员也捕获 RuntimeException ......但这从来不是语言设计者的意图)。

If you were to catch OutOfMemoryError what on earth would you do? The VM is out of memory, basically all you can do is exit. You probably cannot even open a dialog box to tell them you are out of memory since that would take memory :-)

如果您要捕获 OutOfMemoryError,您到底会怎么做?虚拟机内存不足,基本上你能做的就是退出。您甚至可能无法打开一个对话框来告诉他们您的内存不足,因为这会占用内存:-)

The VM throws an OutOfMemoryError when it is truly out of memory (indeed all Errors should indicate unrecoverable situations) and there should really be nothing you can do to deal with it.

当 VM 真的内存不足时(实际上所有错误都应该表示不可恢复的情况),VM 会抛出 OutOfMemoryError 并且你真的应该无能为力。

The things to do are find out why you are running out of memory (use a profiler, like the one in NetBeans) and make sure you don't have memory leaks. If you don't have memory leaks then increase the memory that you allocate to the VM.

要做的事情是找出内存不足的原因(使用分析器,如 NetBeans 中的分析器)并确保没有内存泄漏。如果没有内存泄漏,则增加分配给 VM 的内存。

回答by Stephen C

In general, it is a bad idea to try to catch and recover from an OOM.

通常,尝试捕获 OOM 并从中恢复是一个坏主意。

  1. An OOME could also have been thrown on other threads, including threads that your application doesn't even know about. Any such threads will now be dead, and anything that was waiting on a notify could be stuck for ever. In short, your app could be terminally broken.

  2. Even if you do successfully recover, your JVM may still be suffering from heap starvation and your application will perform abysmally as a result.

  1. OOME 也可能在其他线程上抛出,包括您的应用程序甚至不知道的线程。任何这样的线程现在都将死掉,任何等待通知的东西都可能永远被卡住。简而言之,您的应用程序可能会被最终破坏。

  2. 即使您成功恢复,您的 JVM 仍可能遭受堆饥饿,因此您的应用程序将执行得非常糟糕。

The best thing to do with an OOME is to let the JVM die.

对 OOME 最好的做法是让 JVM 消亡。

(This assumes that the JVM doesdie. For instance OOMs on a Tomcat servlet thread do not kill the JVM, and this leads to the Tomcat going into a catatonic state where it won't respond to any requests ... not even requests to restart.)

(这假设 JVM确实死了。例如,Tomcat servlet 线程上的 OOM 不会杀死 JVM,这会导致 Tomcat 进入紧张状态,它不会响应任何请求……甚至不响应任何请求重新开始。)

EDIT

编辑

I am not saying that it is a bad idea to catch OOM at all. The problems arise when you then attempt to recover from the OOME, either deliberately or by oversight. Whenever you catch an OOM (directly, or as a subtype of Error or Throwable) you should either rethrow it, or arrange that the application / JVM exits.

我并不是说抓住 OOM 是一个坏主意。当您有意或无意地尝试从 OOME 中恢复时,就会出现问题。每当您捕捉到 OOM(直接,或作为 Error 或 Throwable 的子类型)时,您应该重新抛出它,或者安排应用程序/JVM 退出。

Aside: This suggests that for maximum robustness in the face of OOMs an application should use Thread.setDefaultUncaughtExceptionHandler()to set a handler that will cause the application to exit in the event of an OOME, no matter what thread the OOME is thrown on. I'd be interested in opinions on this ...

旁白:这表明,为了在面对 OOM 时获得最大的稳健性,应用程序应该使用Thread.setDefaultUncaughtExceptionHandler()设置一个处理程序,该处理程序将导致应用程序在发生 OOME 时退出,无论 OOME 被抛出到哪个线程上。我对这方面的意见很感兴趣......

The only other scenario is when you know for surethat the OOM has not resulted in any collateral damage; i.e. you know:

唯一的另一种情况是当您确定OOM 没有导致任何附带损害时;即你知道:

  • what specifically caused the OOME,
  • what the application was doing at the time, and that it is OK to simply discard that computation, and
  • that a (roughly) simultaneous OOME cannot have occurred on another thread.
  • 是什么导致了 OOME,
  • 应用程序当时在做什么,并且可以简单地丢弃该计算,并且
  • (大致)同时的 OOME 不可能发生在另一个线程上。

There are applications where it is possible to know these things, but for most applications you cannot know for sure that continuation after an OOME is safe. Even if it empirically "works" when you try it.

有些应用程序可以知道这些事情,但对于大多数应用程序,您无法确定 OOME 之后的继续是安全的。即使在您尝试时它凭经验“有效”。

(The problem is that it a formal proof is required to show that the consequences of "anticipated" OOMEs are safe, and that "unanticipated" OOME's cannot occur within the control of a try/catch OOME.)

(问题是需要一个正式的证据来证明“预期的”OOME 的后果是安全的,并且“未预期的”OOME 不会发生在 try/catch OOME 的控制范围内。)

回答by cHao

The only reason i can think of why catching OOM errors could be that you have some massive data structures you're not using anymore, and can set to null and free up some memory. But (1) that means you're wasting memory, and you should fix your code rather than just limping along after an OOME, and (2) even if you caught it, what would you do? OOM can happen at any time, potentially leaving everything half done.

我能想到为什么要捕获 OOM 错误的唯一原因可能是您有一些不再使用的大量数据结构,并且可以设置为 null 并释放一些内存。但是 (1) 这意味着你在浪费内存,你应该修复你的代码而不是在 OOME 之后一瘸一拐地走,并且 (2) 即使你抓住了它,你会怎么做?OOM 随时都可能发生,这可能会使所有事情都完成一半。

回答by BalusC

You canrecover from it:

可以从中恢复:

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

}

But does it make sense? What else would you like to do? Why would you initially allocate that much of memory? Is less memory also OK? Why don't you already make use of it anyway? Or if that's not possible, why not just giving the JVM more memory from the beginning on?

但这有意义吗?你还想做什么?为什么最初要分配那么多内存?内存少也行吗?你为什么不已经使用它呢?或者,如果这是不可能的,为什么不从一开始就为 JVM 提供更多内存?

Back to your questions:

回到你的问题:

1: is there any real word scenarios when catching java.lang.OutOfMemoryError may be a good idea?

1:在捕捉 java.lang.OutOfMemoryError 可能是一个好主意时,是否有任何真实的场景?

None comes to mind.

没有想到。

2: if we catching java.lang.OutOfMemoryError how can we sure that catch handler doesn't allocate any memory by itself (any tools or best practicies)?

2:如果我们捕获 java.lang.OutOfMemoryError ,我们如何确保 catch 处理程序不会自行分配任何内存(任何工具或最佳实践)?

Depends on what has caused the OOME. If it's declared outside the tryblock and it happened step-by-step, then your chances are little. You maywant to reserve some memory space beforehand:

取决于是什么导致了 OOME。如果它是在try块外声明的,并且是逐步发生的,那么您的机会就很小。您可能需要预先保留一些内存空间:

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

and then set it to zero during OOME:

然后在 OOME 期间将其设置为零:

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

Of course this makes all with all no sense ;) Just give JVM sufficient memory as your applictation require. Run a profiler if necessary.

当然,这一切都毫无意义;) 只需根据您的应用程序需要给 JVM 足够的内存。如有必要,运行分析器。

回答by Rob Heiser

Yes, the real question is "what are you going to do in the exception handler?" For almost anything useful, you'll allocate more memory. If you'd like to do some diagnostic work when an OutOfMemoryError occurs, you can use the -XX:OnOutOfMemoryError=<cmd>hook supplied by the HotSpot VM. It will execute your command(s) when an OutOfMemoryError occurs, and you can do something useful outside of Java's heap. You really want to keep the application from running out of memory in the first place, so figuring out why it happens is the first step. Then you can increase the heap size of the MaxPermSize as appropriate. Here are some other useful HotSpot hooks:

是的,真正的问题是“你打算在异常处理程序中做什么?” 对于几乎所有有用的东西,您将分配更多内存。如果你想在 OutOfMemoryError 发生时做一些诊断工作,你可以使用-XX:OnOutOfMemoryError=<cmd>HotSpot VM 提供的钩子。它会在 OutOfMemoryError 发生时执行你的命令,你可以在 Java 的堆之外做一些有用的事情。您确实希望首先避免应用程序耗尽内存,因此弄清楚为什么会发生这种情况是第一步。然后可以适当增加 MaxPermSize 的堆大小。以下是一些其他有用的 HotSpot 钩子:

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

See the full list here

此处查看完整列表

回答by Yishai

There are definitely scenarios where catching an OOME makes sense. IDEA catches them and pops up a dialog to let you change the startup memory settings (and then exits when you are done). An application server might catch and report them. The key to doing this is to do it at a high level on the dispatch so that you have a reasonable chance of having a bunch of resources freed up at the point where you are catching the exception.

在某些情况下,捕获 OOME 是有意义的。IDEA 捕获它们并弹出一个对话框,让您更改启动内存设置(完成后退出)。应用程序服务器可能会捕获并报告它们。这样做的关键是在调度的高层次上进行,这样您就有合理的机会在捕获异常时释放大量资源。

Besides the IDEA scenario above, in general the catching should be of Throwable, not just OOM specifically, and should be done in a context where at least the thread will be terminated shortly.

除了上面的 IDEA 场景之外,通常捕获应该是 Throwable,而不仅仅是 OOM,并且应该在至少线程将很快终止的上下文中完成。

Of course most times memory is starved and the situation is not recoverable, but there are ways that it makes sense.

当然,大多数时候内存不足并且情况不可恢复,但有一些方法是有意义的。

回答by Michael Kuhn

Yes, there are real-world scenarios. Here's mine: I need to process data sets of very many items on a cluster with limited memory per node. A given JVM instances goes through many items one after the other, but some of the items are too big to process on the cluster: I can catch the OutOfMemoryErrorand take note of which items are too big. Later, I can re-run just the large items on a computer with more RAM.

是的,有真实世界的场景。这是我的:我需要在每个节点内存有限的集群上处理非常多项目的数据集。一个给定的 JVM 实例一个接一个地处理许多项,但有些项太大而无法在集群上处理:我可以捕捉OutOfMemoryError并记下哪些项太大。稍后,我可以在具有更多 RAM 的计算机上重新运行大型项目。

(Because it's a single multi-gigabyte allocation of an array that fails, the JVM is still fine after catching the error and there's enough memory to process the other items.)

(因为它是一个数组的单个多 GB 分配失败,JVM 在捕获错误后仍然正常,并且有足够的内存来处理其他项目。)

回答by Tony Simons

I have an application that needs to recover from OutOfMemoryError failures, and in single-threaded programs it always works, but sometimes doesn't in multi-threaded programs. The application is an automated Java testing tool that executes generated test sequences to the maximum possible depth on test classes. Now, the UI must be stable, but the test engine can run out of memory while growing the tree of test cases. I handle this by the following kind of code idiom in the test engine:

我有一个应用程序需要从 OutOfMemoryError 故障中恢复,并且在单线程程序中它始终有效,但有时在多线程程序中不起作用。该应用程序是一个自动化的 Java 测试工具,它在测试类上最大可能地执行生成的测试序列。现在,UI 必须稳定,但测试引擎可能会在增长测试用例树时耗尽内存。我在测试引擎中通过以下类型的代码习语来处理这个问题:

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

This works every time in single-threaded applications. But I recently put my test engine into a separate worker-thread from the UI. Now, the out of memory may occur arbitrarily in either thread, and it is not clear to me how to catch it.

这在单线程应用程序中每次都有效。但是我最近将我的测试引擎放到了一个独立于 UI 的工作线程中。现在,内存不足可能会在任一线程中任意发生,我不清楚如何捕获它。

For example, I had the OOME occur while the frames of an animated GIF in my UI were being cycled by a proprietary thread that is created behind-the-scenes by a Swing class that is out of my control. I had thought that I had allocated all the resources needed in advance, but clearly the animator is allocating memory every time it fetches the next image. If anyone has an idea about how to handle OOMEs raised in anythread, I would love to hear.

例如,当我的 UI 中的动画 GIF 的帧被专有线程循环时,我发生了 OOME,该线程由不受我控制的 Swing 类在幕后创建。我原以为我已经预先分配了所有需要的资源,但显然动画师每次获取下一张图像时都会分配内存。如果有人对如何处理在任何线程中提出的 OOME 有任何想法,我很想听听。