java.lang.Error的文档说:


错误是Throwable的子类,它指示严重的问题,合理的应用程序不应尝试抓住这些问题。因为java.lang.Errorjava.lang.Throwable的子类,所以我可以捕获这种Throwable类型。

我理解为什么捕获这种异常不是一个好主意。据我了解,如果我们决定捕获它,捕获处理程序不应自行分配任何内存。否则,将再次抛出OutOfMemoryError。 />如果我们决定捕获java.lang.OutOfMemoryError,如何确保捕获处理程序本身不会分配任何内存(任何工具或最佳实践)?


评论

类似的问题:stackoverflow.com/questions/1692230/…和stackoverflow.com/questions/352780/…

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

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

另请参见stackoverflow.com/questions/14376924/…

#1 楼

在许多情况下,您可能希望了解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");
        }
    }
}

该代码的输出:使用我选择的日志记录框架,然后继续释放资源并以干净的方式关闭。可能发生的最坏情况是什么? JVM快要死了(或已经死了),通过捕获Error至少有机会进行清理。
警告:您必须将捕获此类错误的目标仅放在可能进行清理的地方。不要在任何地方盖上Q4312079q或那样胡扯。

评论


同意,我将实验发布到新答案上。

–史密斯先生
2011年9月9日在7:25

“您永远不能100%地确定JVM处于可修复状态”:因为OutOfMemoryError可能是从使您的程序处于不一致状态的点抛出的,因为它可以随时抛出。参见stackoverflow.com/questions/8728866/…

–雷德瓦尔德
2012年1月10日13:04

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

–RoboAlex
2013年9月17日下午0:47



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

– Stephen C
2014年2月8日在7:47

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

–查尔斯·罗斯(Charles Roth)
16年5月5日在19:02

#2 楼

您可以从中恢复:

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提供足够的内存。如有必要,运行分析器。

评论


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

–沃尔夫
2010年4月21日,0:50

@Wolph:然后给JVM更多的内存! O_o所有这一切的确没有道理;)

– BalusC
10年4月21日在0:58



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

–史密斯先生
2011年9月9日在8:19



为什么不简单地将其设置为null?

–起搏器
2011年12月3日15:47

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

–user207421
17年11月15日在22:40



#3 楼

通常,尝试从OOM中捕获和恢复是一个坏主意。知道关于。现在,任何此类线程都将失效,等待通知的所有内容都将永远卡住。简而言之,您的应用程序可能会最终崩溃。
即使您成功恢复了,您的JVM仍然可能遭受堆饥饿的困扰,结果您的应用程序将运行得非常糟糕。

OOME要做的事情就是让JVM死掉。

(这假设JVM确实死了。例如,Tomcat servlet线程上的OOM不会杀死JVM,这导致Tomcat进入一种Catatonic状态,在此状态下它不会响应任何内容请求...甚至没有请求重新启动的请求。)当您随后有意或无意地从OOME中恢复时,就会出现问题。每当您捕获OOM(直接或作为Error或Throwable的子类型)时,都应该重新抛出它,或者安排应用程序/ JVM退出。面对OOM,应用程序应使用Thread.setDefaultUncaughtExceptionHandler()设置一个处理程序,无论OOME抛出什么线程,该处理程序都会在OOME发生时导致应用程序退出。我会对这方面的意见感兴趣...

唯一的其他情况是,当您确定OOM并未导致任何附带损害时;也就是说,您知道:


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

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

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

评论


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

–丹尼斯·巴珍诺夫(Denis Bazhenov)
2010-4-21在0:56

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

– Stephen C
2010-4-21的2:48

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

–丹尼斯·巴珍诺夫(Denis Bazhenov)
2010-4-21的3:28

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

– Stephen C
2010-4-21在4:02



这不仅仅是追赶OOME的问题。您还需要从中恢复。如果线程应该(例如)通知另一个线程,但是又收到了OOME,该如何恢复呢?当然,JVM不会死。但是,由于线程因等待捕获OOME而重新启动线程而导致等待通知,因此应用程序可能会停止运行。

– Stephen C
17年11月15日在22:46



#4 楼

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

评论


因此,您是否有类似byte [] bytes = new byte [length]的代码?为什么不早点检查尺寸呢?

–雷德瓦尔德
2012年1月10日13:07

因为相同的大小可以容纳更多的内存。我通过了例外,因为在大多数情况下,一切都会好起来的。

–迈克尔·库恩(Michael Kuhn)
2012年1月10日13:24

#5 楼

在某些情况下,捕获OOME绝对是有意义的。 IDEA会捕获它们,并弹出一个对话框,让您更改启动内存设置(完成后退出)。应用程序服务器可能会捕获并报告它们。做到这一点的关键是在调度上进行高级别的操作,以便您有合理的机会在捕获异常的时候释放出大量资源。通常,在上面的IDEA场景中,捕获应该是Throwable的,而不仅仅是OOM,并且应该在至少线程将很快终止的情况下进行。

当然大多数情况下,挨饿,情况无法挽回,但是有很多方法可以解决。

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


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

评论


另一个选择是计算可以处理的位图的大小,而不是尝试失败。 “例外情况应使用例外”-我认为有人说过。但我会说,您的解决方案似乎是最简单的方法,也许不是最好的方法,但可能是最简单的方法。

– jontejj
13年2月7日在12:15

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

–丹尼尔·奥尔德(Daniel Alder)
19/12/4在13:52

#7 楼

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

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


在此处查看完整列表

评论


甚至比您想像的还要糟糕。因为OutOfMemeoryError可以在程序中的任何点抛出(不仅是从新语句中抛出),所以当您捕获指令时,程序将处于未定义状态。

–雷德瓦尔德
2015年11月20日,9:25

#8 楼

我有一个需要从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


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

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

评论


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

–史密斯先生
2011年9月9日在7:59

#9 楼

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

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

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

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

评论


如果在catch块内执行ll = null怎么办?

– Naanavanalla
19年8月11日在11:33

#10 楼

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

#11 楼

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



捕获java.lang.OutOfMemoryError时是否存在任何真实的单词方案可能是一个好主意?



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

#12 楼

我只是遇到了一个捕获OutOfMemoryError似乎可行并且可行的情况。

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

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

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

#13 楼


取决于您如何定义“好”。我们在有问题的Web应用程序中执行此操作,并且它在大多数情况下都有效(感谢,由于不相关的修复,现在OutOfMemory不会发生)。但是,即使抓住了它,它也可能会破坏一些重要的代码:如果您有多个线程,则其中的任何一个都会导致内存分配失败。因此,根据您的应用程序的不同,仍然有10--90%的可能性会导致它不可逆转地损坏。
据我所知,大量栈的展开将使大量引用无效,因此释放了那么多内存,您不应该不用担心。

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

#14 楼

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

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

VM真正内存不足时会抛出OutOfMemoryError(实际上,所有错误都应指示

要做的事就是找出为什么内存不足(使用探查器,就像NetBeans中的探查器一样)。并确保您没有内存泄漏。如果没有内存泄漏,请增加分配给VM的内存。

评论


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

– Tim Bender
2010-4-21在5:29

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

–豆腐啤酒
2010-4-21在7:13

我想Tim意味着即使在内存不足的情况下,您仍然可以执行一些工作。例如,可能有足够的内存来打开对话框。

–斯蒂芬·埃勒特(Stephen Eilert)
2010年6月8日在18:57