捕获java.lang.OutOfMemoryError?

java.lang.Error 文档说:

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

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

我明白为什么抓这种例外是不好的主意。 据我所知,如果我们决定去捕捉它,catch处理程序不应该自己分配任何内存。 否则OutOfMemoryError将被再次抛出。

所以,我的问题是:

  1. 捕捉java.lang.OutOfMemoryError可能是一个好主意吗?
  2. 如果我们决定捕获java.lang.OutOfMemoryError ,那么我们怎样才能确定catch处理程序本身没有分配任何内存(任何工具或最佳实践)?

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

有许多情况下,您可能希望捕获一个OutOfMemoryError ,根据我的经验(在Windows和Solaris JVM上),很少出现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:在捕获java.lang.OutOfMemoryError时是否有任何真正的单词情景可能是一个好主意?

没有人想到。

2:如果我们捕获java.lang.OutOfMemoryError,我们如何确定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足够的内存。 如有必要运行分析器。

一般来说,试图从OOM中捕捉和恢复是一个坏主意。

  1. OOME也可能在其他线程上抛出,包括应用程序甚至不知道的线程。 任何这样的线程现在都将死亡,任何等待通知的东西都会永久停留。 总之,你的应用程序可能会被终止。

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

使用OOME最好的办法是让JVM死亡。

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

编辑

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

另外:这表明为了在面对OOM时最大的健壮性,一个应用程序应该使用Thread.setDefaultUncaughtExceptionHandler()来设置一个处理程序,在OOME的情况下会导致应用程序退出,而不管OOME抛出什么线程。 我会对这个意见感兴趣…

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

  • 什么具体造成了OOME,
  • 当时的应用程序正在做什么,可以简单的放弃这个计算,而且
  • 一个(大致)同步的OOME不能在另一个线程上发生。

有一些应用程序可以知道这些事情,但是对于大多数应用程序来说,在OOME安全之后你不能确定是否继续。 即使它在经验上“有效”,当你尝试。

(问题在于需要一个正式的证明来证明“预期的”OOME的后果是安全的,而“意料之外”的OOME不能在try / catch OOME的控制下发生。

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

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

有一些情况下,捕捉OOME是有道理的。 IDEA捕获它们并弹出一个对话框,让您更改启动内存设置(然后在完成后退出)。 应用程序服务器可能会捕获并报告它们。 做到这一点的关键是在调度的高层次上做到这一点,以便您有一个合理的机会,在捕捉异常的地方释放一堆资源。

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

当然,大多数时候记忆是饿死的,情况是不可恢复的,但有一些方法是有道理的。

我遇到了这个问题,因为我想知道在我的情况下是否抓住OutOfMemoryError是一个好主意。 我在这里部分地回答,以显示另一个例子,当发现这个错误对某人(也就是我)是有意义的,并且部分地发现在我的情况下这是否是一个好主意(当我是一个优秀的初级开发者,我永远不会对于我写的任何一行代码来说太确定了)。

无论如何,我正在开发一个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时执行一些诊断工作,则可以使用HotSpot VM提供的-XX:OnOutOfMemoryError=<cmd>钩子。 当发生OutOfMemoryError时,它会执行你的命令,你可以在Java的堆外做一些有用的事情。 你真的想保持应用程序的内存不足,所以弄清楚为什么会发生是第一步。 然后,您可以根据需要增加MaxPermSize的堆大小。 这里有一些其他有用的HotSpot钩子:

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

在这里查看完整列表

我有一个应用程序需要从OutOfMemoryError失败中恢复,并且在单线程程序中它总是可以工作,但有时不会在多线程程序中。 该应用程序是一个自动化的Java测试工具,可以在测试类上执行最大可能深度的生成测试序列。 现在,用户界面必须是稳定的,但是测试引擎在扩大测试用例树的同时可能会耗尽内存。 我在测试引擎中通过以下代码习惯用法来处理这个问题:

 boolean isOutOfMemory = false;  //用于报告的标志
尝试{
    SomeType largeVar;
    //分配越来越多的主循环到largeVar
    //可能终止OK,或引发OutOfMemoryError
 }
 catch(OutOfMemoryError ex){
    // largeVar现在超出范围,垃圾也是
   的System.gc();  / /清理largeVar数据
    isOutOfMemory = true;  //标志可供使用
 }
 //程序测试标志报告恢复

这在单线程应用程序中每次都能正常工作。 但是我最近把我的测试引擎放入了一个独立的工作线程。 现在,任何一个线程都可能出现内存不足的现象,而我怎么去捕捉这个内存呢?

例如,我有OOME发生,而在我的用户界面中的动画GIF的框架正在被一个专有的线程循环,这是一个由我不能控制的Swing类在幕后创建的。 我以为我已经预先分配了所有需要的资源,但显然动画师每次获取下一张图像时都会分配内存。 如果有人对如何处理在任何线程中提出的OOME有一个想法,我很乐意听到。

我可以想到为什么捕捉OOM错误的唯一原因可能是你有一些海量的数据结构,你不再使用,可以设置为空,释放一些内存。 但是(1)这意味着你正在浪费记忆,你应该修复你的代码,而不是在OOME之后跛行,(2)即使你抓到它,你会怎么做? OOM可以在任何时候发生,可能会让一切都完成。

对于问题2,我已经看到了BalusC提出的解决方案。

  1. 捕捉java.lang.OutOfMemoryError可能是一个好主意吗?

我想我刚刚遇到一个很好的例子。 当awt应用程序正在调度消息时,在stderr上显示未捕获的OutOfMemoryError,并停止处理当前消息。 但是应用程序一直在运行! 用户仍然可以发出其他命令而不知道现场背后发生的严重问题。 特别是当他不能或不遵守标准的错误。 因此,捕捉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"); } } 

但是,只要在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 catched!!"); System.out.println("size:" +ll.size()); } System.out.println("Test finished"); } } 

第一个程序运行良好,因为当达到捕获时,JVM检测到列表不再被使用(这个检测也可以在编译时进行优化)。 所以当我们到达打印声明时,堆内存已经几乎完全释放了,所以我们现在有一个很大的回旋余地。 这是最好的情况。

但是,如果代码被安排在列表中,那么在OOME被捕获后使用ll ,JVM将无法收集它。 这发生在第二个片段。 由一个新的Long创建触发的OOME被捕获,但是很快我们创建了一个新的Object( System.out,println一个String System.out,println行),并且堆几乎已经满了,所以抛出了一个新的OOME。 这是最糟糕的情况:我们试图创建一个新的对象,我们失败了,我们抓住了OOME,是的,但现在第一个需要新的堆内存的指令(例如:创建一个新的对象)将抛出一个新的OOME。 考虑一下,我们还有什么可以做的呢? 可能只是退出。 因此没用。

JVM不收集资源的原因之一是真的很可怕:与其他线程的共享资源也在使用它。 任何拥有大脑的人都可以看到,如果插入某种非实验性的应用程序,OOME可能会有多危险。

我正在使用Windows x86 32位JVM(JRE6)。 每个Java应用程序的默认内存为64MB。

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

如果你要赶上OutOfMemoryError你会怎么做? 虚拟机内存不足,基本上你所能做的就是退出。 你可能甚至不能打开一个对话框告诉他们你内存不足,因为这将需要内存:-)

虚拟机在内存不足时会抛出一个OutOfMemoryError(实际上,所有的错误都应该表明不可恢复的情况),并且应该没有什么办法可以处理它。

要做的事情是找出你为什么内存不足(使用一个分析器,就像NetBeans中的分析器),并确保你没有内存泄漏。 如果没有内存泄漏,则增加分配给虚拟机的内存。

我只是有一个场景,捕捉OutOfMemoryError似乎是有道理的,似乎工作。

场景:在Android应用程序中,我想以最高分辨率显示多个位图,并且希望能够流利地缩放它们。

由于流畅的缩放,我想要在内存中的位图。 然而,Android在内存方面有限制,这些内存依赖于设备,而且很难控制。

在这种情况下,读取位图时可能会出现OutOfMemoryError。 在这里,如果我抓住它,然后继续降低分辨率,这将有所帮助。

  1. 取决于你如何定义“好”。 我们在我们的bug的web应用程序中这样做,它大部分时间工作 (谢天谢地,现在OutOfMemory不会发生,由于一个不相关的修复)。 但是,即使你抓住它,它仍然可能会破坏一些重要的代码:如果你有多个线程,内存分配可能会失败的任何一个。 所以,根据您的应用程序,仍有10-90%的机会被不可逆转地破坏。
  2. 据我所知,重叠堆放在途中将无效的这么多的参考,从而释放你不应该在乎的这么多的记忆。

编辑:我建议你试试看。 说,写一个递归调用一个递增分配内存的函数的程序。 抓住OutOfMemoryError ,看看你是否可以从这一点有意义的继续。 根据我的经验,你将能够,但在我的情况下,它发生在WebLogic服务器下,所以可能会有一些黑魔法。