2010-04-20 76 views
80

Documentationjava.lang.Error说:捕捉java.lang.OutOfMemoryError?

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

但由于java.lang.Errorjava.lang.Throwable一个子类,我能赶上这个Throwable类型。

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

所以,我的问题是:

  1. 醒目java.lang.OutOfMemoryError时,可能是一个好主意是否有任何真正的单词的场景?
  2. 如果我们决定赶上java.lang.OutOfMemoryError,我们如何确定catch处理程序本身不分配任何内存(任何工具或最佳实践)?
+2

类似的问题:http://stackoverflow.com/questions/1692230/is-it-possible-to-catch-out-of-memory-exception-in-java和http://stackoverflow.com/questions/ 352780/when-to-catch-java-lang-error – BalusC 2010-04-20 23:19:42

+0

对于你的第一个问题,我会补充说我会捕获OutOfMemoryError以便(至少尝试)通知用户这个问题。以前,catch(Exception e)子句没有捕获到该错误,并且没有向用户显示任何反馈。 – 2013-10-25 10:14:27

+1

有一些特定的情况,例如分配一个巨大的数组,在那里可以捕获围绕该操作的OOM错误并且恢复得相当好。但将try/catch放在一大块代码周围,试图恢复干净并继续运行可能不是一个好主意。 – 2014-07-18 11:55:55

回答

57

我同意并不同意这里的大多数回复。

有很多情况下,你可能希望赶上OutOfMemoryError和我的经验(在Windows和Solaris JVM上),只有极少数情况下是对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) {}或者这样的废话。

+0

糟糕 “应该是个不错的主意,因为你可以100%”应该是“一个坏主意,因为你永远不会100%” – Chris 2010-04-21 06:35:33

+3

即使信誉低,你仍然可以编辑自己的答案。只需点击答案左下角的编辑链接即可。 – 2010-04-21 07:00:55

+2

感谢您的信息和编辑。我确实在寻找编辑按钮,但我必须有选择地盲目。 – Chris 2010-04-21 08:17:26

0
  1. 取决于你如何定义“好”。我们在我们的越野车Web应用程序中这样做,并且它大多数时间在上工作(幸运的是,现在的OutOfMemory由于不相关的修复而未发生)。但是,即使你抓住它,它仍然可能会破坏一些重要的代码:如果你有多个线程,内存分配可能会失败。所以,根据您的应用程序,仍有10-90%的机会被不可逆转地破坏。
  2. 据我所知,在栈道上沉重的堆栈展开会使这么多引用无效,从而释放你不应该关心的大量内存。

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

0

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

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

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

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

+6

您的帖子永久存在的误解是,OOM​​表示JVM内存不足。相反,它实际上表明JVM不能分配它被指示的所有内存。也就是说,如果JVM具有10B的空间,并且“新建”了一个100B的对象,它将会失败,但是您可以转向并且“新建”5B对象并且没问题。 – 2010-04-21 05:29:59

+1

如果我只需要5B他们为什么要求10B?如果你正在根据试验和错误进行分配,那么你做错了。 – TofuBeer 2010-04-21 07:13:03

+2

我想蒂姆意味着即使有OutOfMemory情况,你仍然可以执行一些工作。例如,可能有足够的内存来打开对话框。 – 2010-06-08 18:57:16

13

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

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

  2. 即使您成功恢复,您的JVM仍可能会遭受堆匮乏,并且您的应用程序将因此而表现糟糕。

使用OOME最好的做法是让JVM死亡。 (假设JVM 确实是 die。例如,Tomcat servlet线程上的OOM不会终止JVM,这会导致Tomcat进入紧张状态,不会响应任何请求。 ..甚至不要求重启。)

编辑

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

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

唯一的另一种情况是当你知道肯定 OOM没有造成任何附带损害;例如,你知道:

  • 具体是什么造成了OOME,
  • 什么应用程序在做的时候,那是OK简单地丢弃计算,并
  • 一个(大致)同时OOME不能在另一个线程上发生。

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

(的问题是,它的正式证明需要证明的“预期” OOMEs后果是安全的,并认为“意料之外” OOME的一个try/catch OOME的控制范围内就不会发生。)

+0

是的,我同意你的意见。一般来说,这是个坏主意。但为什么我有可能赶上它? :) – 2010-04-21 00:56:09

+0

@dotsid - 1),因为有些情况下应该抓住它,2)因为不可能捕捉到OOM,将会对Java运行时的语言和/或其他部分产生负面影响。 – 2010-04-21 02:48:06

+1

你说:“因为有些情况下你应该赶上它”。所以这是我原来的问题的一部分。 *您想要抓住OOME的情况是什么? – 2010-04-21 03:28:34

2

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

+2

那么,我们有这种行为的“弱引用和朋友”... – 2010-04-21 00:53:39

24

可以歇着:

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: is there any real word scenarios when catching java.lang.OutOfMemoryError may be a good idea?

没有想到的。

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

取决于造成OOME的原因。如果在try区块外宣布它,并且它一步一步发生,那么您的机会很少。您可能要预留事前一些内存空间:OOME期间

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

然后将其设置为零:

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

当然,这使所有的一切没有意义的;)只要给JVM足够记忆力按照您的应用要求。如有必要运行分析器。

+1

即使预留空间它不能保证工作的解决方案。这个空间也可能被其他线程占用;) – Wolph 2010-04-21 00:50:02

+0

@Wolph:然后给JVM更多的内存! O_o所有这一切确实没有意义;) – BalusC 2010-04-21 00:58:05

+0

谁给了-1,为什么?这是一个非常有效的答案,也是唯一提供了一些可能的解决方案的答案。 – Wolph 2010-04-21 01:04:04

4

是的,真正的问题是“你将在异常处理程序中做什么?”几乎任何有用的东西,你都会分配更多的内存。如果您想在出现OutOfMemoryError时执行一些诊断工作,则可以使用由HotSpot VM提供的-XX:OnOutOfMemoryError=<cmd>挂钩。当发生OutOfMemoryError时,它会执行你的命令,你可以在Java堆外做一些有用的事情。你真的想保持应用程序的内存不足,所以弄清楚为什么会发生这是第一步。然后,您可以根据需要增加MaxPermSize的堆大小。这里有一些其他有用的热点挂钩:

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

查看完整列表here

+0

这比你想象的还要糟糕。由于'OutOfMemeoryError' [可以在程序中的任意位置引发](http://stackoverflow.com/a/8729011/545127)(不仅来自'new'语句),当您的程序处于未定义状态时你赶上演出。 – Raedwald 2015-11-20 09:25:51

8

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

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

当然,大多数时候记忆都很匮乏,情况不可恢复,但有些方法是有道理的。

+1

+1因为“捕捉OOM有意义”。 – Pacerier 2014-07-18 12:00:55

10

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

(因为它是失败的数组的一个多GB的分配,JVM仍然捕捉错误后罚款,有足够的内存来处理其他项目。)

+0

所以你有像'byte [] bytes = new byte [length]''的代码?为什么不在早期检查'size'? – Raedwald 2012-01-10 13:07:40

+1

因为相同的'大小'会更好,更多的内存。我通过这个例外,因为在大多数情况下,一切都会好的。 – 2012-01-10 13:24:06

3

我有一个应用程序,需要从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 

这在单线程应用程序中每次都有效。但是我最近把我的测试引擎放到了用户界面的一个单独的工作线程中。现在,任何一个线程都可能出现内存不足的情况,我不清楚如何去捕捉它。

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

+0

你应该问它作为一个单独的问题,它不会在这里显示出来...... – Grooveek 2011-05-31 20:41:27

+0

在一个单线程应用程序中,如果你不再使用一些有问题的新对象,其创建会抛出错误,这些可能会被收集catch语句。但是,如果JVM检测到该对象稍后可能会使用,则无法收集该应用程序,并且该应用程序会崩溃。在这个主题中看到我的答案。 – 2011-09-09 07:59:00

2

OOME可以被捕获,但通常是无用的,这取决于JVM是否能够在达到catch时垃圾收集一些对象,以及当时剩下多少堆内存。

实例:在我的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"); 
    } 
} 

然而,仅仅添加一行在抓会告诉你我说的是:

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

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

但是,如果代码的排列方式如列表ll在获取OOME之后使用,则JVM无法收集它。这发生在第二个片段中。由一个新的Long创建触发的OOME很快被捕获,但是很快我们创建了一个新对象(System.out,println行中的一个字符串),并且堆几乎已满,所以引发了一个新的OOME。这是最糟糕的情况:我们尝试创建一个新对象,但我们失败了,我们捕捉到了OOME,是的,但现在第一条需要新堆内存的指令(例如:创建一个新对象)将抛出一个新的OOME。考虑一下,我们还有什么可以做的,只留下这么少的记忆?可能只是退出。因此没用。

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

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

6

我遇到了这个问题,因为我想知道在我的情况下捕捉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; 
    } 
} 

这样我管理根据自己的,或者说他们的用户的需求和期望,以提供更多和更少的功能强大的设备。

+1

另一种选择是计算您可以处理多大的位图,而不是尝试和失败。 “例外情况应该用于例外情况” - 我想有人说。但我会说,你的解决方案似乎是最简单的解决方法,也许不是最好的,但可能是最简单的。 – jontejj 2013-02-07 12:15:06

2

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

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

我想我刚碰到一个很好的例子。当awt应用程序调度消息时,stderr上会显示未捕获的OutOfMemoryError,并停止处理当前消息。但该应用程序保持运行!用户仍然可以发出其他命令而不知道场景后面发生的严重问题。特别是当他不能或不遵守标准错误时。因此,捕捉oom异常并提供(或至少是建议)应用程序重启是需要的。

1

我只是有一个场景,捕捉OutOfMemoryError似乎有意义,似乎工作。

情景:在一个Android应用程序,我想在尽可能高的分辨率,显示多个位图,我希望能够流利地放大他们。

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

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