Catching java.lang.OutOfMemoryError?

Documentation for java.lang.Error says:

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

But as java.lang.Error is a subclass of java.lang.Throwable, I can catch this type of 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 OutOfMemoryError will be thrown again.

So, my question is:

  1. Are there any real world scenarios when catching java.lang.OutOfMemoryError might 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)?
98349 次浏览
  1. 那要看你怎么定义“好”了。我们在我们的错误的 web 应用程序中这样做,它确实工作 大多数时候(谢天谢地,现在 OutOfMemory没有发生,由于一个不相关的修复)。但是,即使您捕获了它,它仍然可能破坏了一些重要的代码: 如果您有多个线程,那么其中任何一个线程的内存分配都可能失败。因此,根据应用程序的不同,仍然有10-90% 的可能性会造成不可逆转的破坏。
  2. 据我所知,在运行过程中进行大量堆栈展开会使如此多的引用失效,从而释放如此多的内存,您不应该关心这个问题。

编辑: 我建议你试一试。例如,编写一个递归调用函数的程序,该函数将逐步分配更多的内存。捕捉 OutOfMemoryError,看看是否可以有意义地继续从这一点。根据我的经验,你将能够,虽然在我的情况下,它发生在 WebLogic 服务器,所以可能有一些黑魔法涉及。

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).

如果你抓住了 OutOfMemory 错误,你到底会怎么做?虚拟机内存不足,基本上你只能退出。您可能甚至不能打开一个对话框告诉他们您的内存不足,因为这会占用内存: -)

当虚拟机确实内存不足时(实际上所有的错误都应该指示不可恢复的情况) ,虚拟机会抛出 OutOfMemory 错误,而且您确实无法处理它。

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.

一般来说,尝试从 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.

使用 OOME 的最佳方法是让 JVM 消亡。

(这里假设 JVM 是的死亡。例如,Tomcat servlet 线程上的 OOM 不会杀死 JVM,这会导致 Tomcat 进入紧张状态,不会响应任何请求... 甚至不会重新启动请求。)

编辑

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.

旁白: 这表明,为了在 OOMs 面前获得最大的健壮性,应用程序应该使用 异常处理程序()来设置一个处理程序,该处理程序将导致应用程序在 OOME 事件中退出,而不管 OOME 被抛到哪个线程上。我想听听你对这件事的看法。

唯一的另一种情况是,当你知道 当然,OOM 没有造成任何附带损害; 也就是说,你知道:

  • 是什么导致了 OOME,
  • 应用程序当时在做什么,以及简单地放弃计算是可以的,并且
  • 不可能在另一个线程上发生(大致)同时的 OOME。

有些应用程序可能知道这些事情,但是对于大多数应用程序,您不能确切地知道在 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.)

我能想到的捕捉 OOM 错误的唯一原因可能是你有一些海量的数据结构,你不再使用,并可以设置为 null 和释放一些内存。但是(1)这意味着您在浪费内存,您应该修复您的代码,而不是仅仅一瘸一拐地跟在 OOME 之后; (2)即使您发现了它,您会怎么做?OOM 可能在任何时候发生,有可能让一切只完成了一半。

你从中恢复过来:

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 更多的内存呢?

Back to your questions:

1: 在捕捉 java.lang 时,有没有什么真正的单词场景? OutOfMemory 错误可能是一个好主意?

None comes to mind.

如果我们能抓到 java.lang。如何确保 catch 处理程序不会自己分配任何内存(任何工具或最佳实践) ?

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

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

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

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

当然,这样做毫无意义;)只需根据应用程序的需要给 JVM 足够的内存。必要时运行分析器。

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

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

参见完整列表 给你

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.

除了上面的 IDEA 场景,一般来说,捕捉应该是针对 Throwable 的,而不仅仅是针对 OOM,而且应该在至少线程将很快终止的情况下进行。

当然,大多数情况下内存不足,这种情况是不可恢复的,但是有一些方法可以解决这个问题。

有许多情况下,您可能希望捕捉到 OutOfMemoryError,根据我的经验(在 Windows 和 Solaris JVM 上) ,只有很少的情况下,OutOfMemoryError是 JVM 的丧钟。

There is only one good reason to catch an OutOfMemoryError and 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的出现是由于块内存分配不能满足堆的剩余资源。

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

证明 OutOfMemoryError并不意味着 catch 块中的 JVM 内存不足:

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) {}或者那样的废话。

是的,有现实世界的情况。我的是这样的: 我需要在一个每个节点内存有限的集群上处理非常多项目的数据集。一个给定的 JVM 实例一个接一个地处理许多条目,但是有些条目太大,无法在集群上处理: 我可以捕获 OutOfMemoryError并注意哪些条目太大。稍后,我可以重新运行只是大项目的计算机上更多的 RAM。

(因为这是一个数组的单个千兆字节分配失败,所以在捕捉到错误之后,JVM 仍然可以正常工作,并且有足够的内存来处理其他项目。)

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:

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

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

例如,当我的用户界面中的动画 GIF 的帧由一个不受我控制的 Swing 类在幕后创建的专有线程进行循环时,出现了 OOME。我原以为我已经提前分配了所有需要的资源,但显然动画师每次获取下一个图像时都在分配内存。如果有人有关于如何处理在 任何线程中提出的 OOME 的想法,我很乐意听听。

An OOME can be caught, but it is going to be generally useless, depending on if the JVM is able to garbage-collect some objects when reaching the catch block, and how much heap memory is left by that time.

Example: in my JVM, this program runs to completion:

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

然而,只要在 catch 块中添加一行代码就可以显示我所说的内容:

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 caught!!");
System.out.println("size:" +ll.size());
}
System.out.println("Test finished");
}
}

第一个程序运行良好,因为当到达 catch 块时,JVM 检测到列表将不再使用(这种检测也可以是在编译时进行的优化)。因此,当我们到达 print 语句时,堆内存几乎完全被释放了,所以我们现在有很大的余地继续操作。这是最好的情况。

但是,如果代码的排列方式是在捕获 OOME 之后使用列表 ll,那么 JVM 就无法收集它。这在第二个代码片段中发生。由一个新的 Long 创建触发的 OOME 被捕获,但是很快我们将创建一个新的 Object (System.out.println行中的一个 String) ,堆几乎满了,因此抛出一个新的 OOME。这是最糟糕的情况: 我们尝试创建一个新对象,但是失败了,我们捕获了 OOME,是的,但是现在第一条需要新堆内存的指令(例如: 创建一个新对象)将抛出一个新的 OOME。想想看,在这个时候,我们还能做些什么呢? 我们的记忆已经所剩无几了?可能只是出去,所以我说没用。

在 JVM 不是垃圾收集资源的原因中,有一个非常可怕: 与其他线程共享的资源也在使用它。任何人都可以看到,如果将捕捉 OOME 添加到任何类型的非实验性应用程序中,会有多么危险。

我使用的是 Windowsx8632bitJVM (JRE6) ,每个 Java 应用程序的默认内存是64MB。

我遇到这个问题是因为我想知道在我的情况下捕获 OutOfMemory 错误是否是一个好主意。我在这里回答部分是为了展示另一个例子,当捕捉到这个错误对某些人(比如我)有意义时,部分是为了找出在我的情况下这是不是一个好主意(作为一个超级初级开发人员,我永远不能太确定我写的任何一行代码)。

不管怎样,我正在开发一个 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;
}
}

通过这种方式,我设法根据用户的需求和期望,提供功能越来越强大的设备。

对于问题2,我已经看到了我建议的解决方案。

  1. 在捕捉 java.lang.OutOfMemory 错误时,有没有什么真正的场景可能是一个好主意?

我想我刚刚遇到了一个很好的例子。当 awt 应用程序发送消息时,标准程序上将显示未捕获的 OutOfMemorial yError,并停止对当前消息的处理。但是应用程序一直在运行!用户仍然可以发出其他命令,而不知道幕后发生的严重问题。特别是当他不能或不遵守标准错误的时候。因此,捕获 oom 异常并提供(或至少建议)应用程序重启是我们所期望的。

我只是有一个场景,捕捉一个 OutOfMemory 错误似乎是有意义的,似乎工作。

场景: 在一个 Android 应用程序中,我想以尽可能高的分辨率显示多个位图,并且我想能够流利地放大它们。

由于可以流畅地进行缩放,我希望在内存中保存位图。然而,Android 在内存方面有一些限制,这些限制依赖于设备,而且很难控制。

在这种情况下,读取位图时可能出现 OutOfMemory 错误。在这里,它有助于如果我赶上它,然后继续与较低的分辨率。