捕获java.lang.OutOfMemoryError?


101

文档java.lang.Error说:

错误是Throwable的子类,它指示严重的问题,即合理的应用程序不应尝试抓住

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

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

所以,我的问题是:

  1. 捕捞java.lang.OutOfMemoryError可能是个好主意吗?
  2. 如果我们决定进行catch java.lang.OutOfMemoryError,如何确保catch处理程序本身不会分配任何内存(任何工具或最佳实践)?


对于您的第一个问题,我要补充一点,我将捕获OutOfMemoryError以便(至少尝试)将问题通知用户。以前,catch(Exception e)子句未捕获该错误,并且未向用户显示任何反馈。
JosepRodríguezLópez2013年

1
在某些特定情况下,例如分配一个巨大的数组,可以在该操作周围捕获OOM错误并可以很好地恢复。但是,将try / catch放在大量的代码周围,然后尝试干净地恢复并继续进行,可能不是一个好主意。
Hot Licks

Answers:


85

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

OutOfMemoryError根据我的经验(在Windows和Solaris JVM上),您可能希望在许多情况下抓住一个机会,而对JVM OutOfMemoryError的丧钟很少见。

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

通常,OutOfMemoryError发生这种情况的原因是块内存分配无法用堆的其余资源满足。

Error被抛出堆载分配的对象是不成功的分配之前相同数量的,现在是下降到运行时间对象以释放可能需要进行清理更加内存引用的时间。在这些情况下,甚至有可能继续执行操作,但这绝对不是一个好主意,因为您永远无法百分百确定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) {}到处遮盖或胡说八道。


同意,我将实验发布到新答案上。
史密斯先生

4
“你永远不能100%肯定的是,JVM处于可修复状态”:因为OutOfMemoryError可能是从已经放置YOUT程序不一致的状态点抛出,因为其可以在抛出的任何时间。见stackoverflow.com/questions/8728866/...
Raedwald

在OpenJdk1.7.0_40中,运行此代码时没有任何错误或异常。甚至我将MEGABYTE更改为GIGABYTE(1024 * 1024 * 1024)。是因为优化程序删除了变量“ byte [] bytes”,因为在其余代码中未使用它吗?
RoboAlex 2013年

“在许多情况下,您可能希望捕获OutOfMemoryError”“只有一个很好的理由捕获OutOfMemoryError”。下定决心!!!
Stephen C

3
当您可能想捕获OutOfMemory错误时的实际情况:当该错误是由于尝试分配具有2G以上元素的数组而引起的。在这种情况下,错误名称有点用词不当,但它仍然是OOM。
查尔斯·罗斯,

31

可以从中恢复:

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,如何确定捕获处理程序本身不会分配任何内存(任何工具或最佳实践)?

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

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

然后在OOME中将其设置为零:

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

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


1
即使保留空间,也不能保证有可行的解决方案。该空间也可能会被其他线程
占用

@Wolph:然后给JVM更多的内存!O_o所有这一切都毫无意义;)
BalusC 2010年

2
第一个代码段有效,因为触发错误的对象是单个BIG对象(数组)。到达catch子句时,它已由需要大量内存的JVM收集。如果您在try块之外,在其他线程中或在同一catch中使用同一对象,则JVM不会收集它,因此无法创建任何种类的新单个对象。例如,第二个片段可能不起作用。
史密斯先生

3
为什么不简单地将其设置为null
Pacerier,2011年

1
@MisterSmith您的评论没有任何意义。大对象不存在。它不是一开始就分配的:它触发了OOM,因此它当然不需要GC。
罗恩侯爵

15

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

  1. 一个OOME也可能引发了其他线程,包括您的应用程序甚至都不知道的线程。现在,任何此类线程都将失效,等待通知的所有内容都将永远卡住。简而言之,您的应用可能会最终崩溃。

  2. 即使您确实成功恢复了,您的JVM仍然可能遭受堆饥饿的困扰,结果您的应用程序将执行得很小。

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

(这假设JVM 确实死了。例如,Tomcat Servlet线程上的OOM不会杀死JVM,这导致Tomcat进入了Catatonic状态,在该状态下它不会响应任何请求...甚至没有响应重新开始。)

编辑

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

旁白:这表明,为了使OOM面对最大的鲁棒性,应用程序应使用Thread.setDefaultUncaughtExceptionHandler()设置一个处理程序,该处理程序将导致应用程序在发生OOME时退出,无论OOME抛出了什么线程。我会对这方面的意见感兴趣...

唯一的其他情况是当您确定 OOM尚未造成任何附带损害时;即你知道:

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

在某些应用程序中,您可能会知道这些事情,但是对于大多数应用程序,您不能肯定地知道在OOME之后继续执行是安全的。即使您凭经验尝试它,“凭经验”也可以。

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


是的,我同意你的看法。一般来说,这是个坏主意。但是为什么我有可能抓住它呢?:)
Denis Bazhenov 2010年

@dotsid-1)因为在某些情况下您应该捕获它,而2)因为无法捕获OOM会对语言和/或Java运行时的其他部分产生负面影响。
斯蒂芬·C

1
您说:“因为在某些情况下您应该抓住它”。所以这是我最初的问题的一部分。什么时候你想赶上OOME是个案?
Denis Bazhenov

1
@dotsid-看到我编辑的答案。我能想到的唯一一种捕获OOM的情况是,当您需要执行此操作以在发生OOM时强制多线程应用程序退出时。您可能想对的所有子类型执行此操作Error
斯蒂芬·C

1
这不仅仅是追赶OOME的问题。您还需要从中恢复。如何做一个线程恢复,如果它被认为(说)通知另一个线程...但它有一个OOME?当然,JVM不会死。但是,由于线程因等待捕获OOME而重新等待线程发出的通知,因此该应用程序可能会停止运行。
斯蒂芬·C

14

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

(因为是数组的单个多千兆字节分配失败,所以在捕获到错误并且有足够的内存来处理其他项之后,JVM仍然可以使用。)


所以你有类似的代码byte[] bytes = new byte[length]吗?为什么不只是size早点检查呢?
拉德瓦尔德2012年

1
因为size用更多的内存也可以。我通过了例外,因为在大多数情况下,一切都会好起来的。
Michael Kuhn 2012年

10

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

除了上面的IDEA方案外,通常,捕获应该是Throwable的,而不仅仅是OOM,并且应该在至少线程将很快终止的情况下进行。

当然,大多数情况下内存不足,情况无法恢复,但是有很多方法可以解决问题。


8

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

这取决于编解码器。想象一下,一个10MB的bmp可能只会导致略大于10MB的堆,而10MB的JPEG将“爆炸”。同样,在我要解析XML的情况下,XML可能会根据内容的复杂性而有很大的不同
Daniel Alder

5

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

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

这里查看完整列表


这比您想像的还要糟糕。因为OutOfMemeoryError 可以在程序中的任何点(不仅是从new语句)引发 an,所以当您捕获该指令时,程序将处于未定义状态。
2015年

5

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

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

每次在单线程应用程序中都可以使用。但是我最近将测试引擎放入了与UI分离的工作线程。现在,内存不足可能在任一线程中任意发生,我不清楚如何捕获它。

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


在单线程应用程序中,如果不再使用某些有问题的新对象,这些对象的创建会引发错误,则可以将它们收集在catch子句中。但是,如果JVM检测到该对象以后可以使用,则无法收集该对象,并且应用程序崩溃。看到我在这个线程的答案。
史密斯先生

4

可以捕获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");
    }  
}

但是,仅在捕获中添加一行就可以向您展示我在说什么:

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会检测到不再使用列表(此检测也可以是在编译​​时进行的优化)。因此,当我们到达print语句时,堆内存几乎全部被释放了,因此我们现在有很大的回旋余地可以继续。这是最好的情况。

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

JVM不收集资源的原因中,有一个确实令人恐惧:与其他线程共享资源也正在使用它。任何有头脑的人都可以看到,如果将OOME插入任何类型的非实验应用中,可能会多么危险。

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


如果我ll=null在挡块内怎么办?
Naanavanalla

3

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


3

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

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

我想我只是遇到了一个很好的例子。当awt应用程序正在调度消息时,未捕获的OutOfMemoryError将显示在stderr上,并且当前消息的处理将停止。但是应用程序仍在运行!用户可能仍然发出其他命令,而没有意识到在幕后发生的严重问题。特别是当他不能或没有遵守标准错误时。因此,需要捕获oom异常并提供(或至少建议)应用程序重新启动。


3

我只是有一个场景,捕获OutOfMemoryError似乎很有意义并且似乎可以正常工作。

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

由于流畅的缩放,我想在内存中保留位图。但是,Android的内存限制取决于设备且难以控制。

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


0
  1. 取决于您如何定义“好”。我们在有缺陷的Web应用程序中执行此操作,并且它在大多数时间都有效(令人遗憾的是,OutOfMemory由于不相关的修复,现在不会发生)。但是,即使您抓住了它,它也可能会破坏一些重要的代码:如果您有多个线程,则其中的任何一个都会导致内存分配失败。因此,根据您的应用程序的不同,仍然有10--90%的可能性被不可逆转地破坏。
  2. 据我了解,大量栈的展开将使如此多的引用失效,从而释放出如此多的内存,您无需在意。

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


-1

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

如果您要捕获OutOfMemoryError,那该怎么办?VM内存不足,基本上您只能做的是退出。您甚至可能无法打开对话框来告诉他们您的内存不足,因为那样会占用内存:-)

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

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


7
您的帖子长期存在一个误解,认为OOM表示JVM内存不足。相反,它实际上表明JVM无法分配它所指示的所有内存。也就是说,如果JVM有10B的空间,而您“更新”一个100B的对象,它将失败,但是您可以转过来并“更新”一个5B的对象,就可以了。
蒂姆·班德

1
如果我只需要5B,他们为什么要10B?如果您基于反复试验进行分配,那么您做错了。
TofuBeer

2
我想Tim意味着即使在内存不足的情况下,您仍然可以执行一些工作。例如,可能有足够的内存来打开对话框。
Stephen Eilert 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.