【问题标题】:Should I catch OutOfMemoryError? [duplicate]我应该抓住 OutOfMemoryError 吗? [复制]
【发布时间】:2012-12-31 20:45:20
【问题描述】:

一般的建议是你不应该捕获 java.lang.Error 除非在特殊情况下,例如见Is it a bad practice to catch Throwable?

我的情况是我有一个程序有时会耗尽内存并抛出 java.lang.OutOfMemoryError。尽管无法从中恢复,但我确实想知道它发生了,所以我希望在日志中看到一些东西和一个非零退出代码。那么这样的做法值得推荐吗?

public static void main(String[] args)
{
    try
    {
        ...
    }
    catch (Exception e)
    {
        e.printStackTrace();
        System.exit(1);
    }
    catch (OutOfMemoryError e)
    {
        e.printStackTrace();
        System.exit(1);
    }
}

另一个程序与此类似,只是它可能是一个消耗所有内存的特定线程。在这种情况下,如果该线程退出,则可以继续处理,我真正想要的只是查看日志并最终获得非零退出代码。那么我应该在那个线程运行方法中捕获 OutOfMemoryError 吗?

【问题讨论】:

  • 如果你抓住了它,你会怎么做?如果你的内存用完了,那么你认为你能记录什么?有什么意义?
  • 在您的代码示例中,printStackTrace 创建了一些对象,并可能引发另一个 OOME,因此不完整,但除此之外,它不会真正造成比您拥有的更多的伤害。
  • 捕获异常并执行其他操作甚至会导致抛出其他 OutOfMemoryError!
  • 关键是要知道它发生了,以便在必要时可以使用更多内存重新运行生产作业,或者删除导致问题的大记录。
  • @SJuan76 显式的System.gc() 完全是多余的。如果有垃圾需要处理,任何理智的 GC 都不会抛出 OOME。

标签: java out-of-memory


【解决方案1】:

在调用堆栈的最顶端设置一个异常屏障,捕获并记录所有Throwables 是非常有意义的。在服务器端代码中,这实际上是常态。如果您确保仅在该级别捕获OutOfMemoryError,而不是更低的级别,那么它很有可能不会损害您的系统:随着调用堆栈展开,为服务请求而创建的所有对象都将变为遥不可及。由于 OOME 很可能恰好发生在对系统造成最大内存压力的线程中,因此所有内存都将被回收,系统的其余部分将能够再次呼吸。

是的,从技术上讲,总是有机会在 finally 块内获得 OOME,从而导致资源泄漏或更糟;或者在一些代码中修改了一个长期存在的全局结构,破坏了它的不变量,但在实践中这不太可能。

在决定您的 OOME 政策时,请记住,您的应用程序会受到许多不可预测的因素的影响,这些因素或多或少可能会降低其稳定性。 OOME 只是该范围内的另一个点,通常它的风险影响不是特别高。

【讨论】:

  • +1 - 捕获所有Trowables(不仅仅是Errors)并记录它们。非常重要。
  • OP:s 代码不可能导致 OOME 的无限循环吧?如果在 e..printStackTrace() 上引发了 OOME,它会被抛出到 catch 块中,而不是 try 块中。所以没有什么可以赶上第二个OOME。还是我理解错了?
  • @Alderath 我不是指那种循环,而是试图重复 OOME 的工作,只是为了结束另一个 OOME,或者尝试服务任何进一步的请求,这都是 OOME因为所有内存都保留在静态/实例字段中。
  • 我的错。也许我把你的陈述断章取义了。但是,对于任何异常的任何类型的重试策略都可能发生无限循环问题。当您尝试重试时,您只需设置一些标志/计数器,以便您尝试无限次重试。
  • 我喜欢堆栈展开的观点。这将首先释放导致问题的所有内存使用,并意味着日志记录应该可以正常工作。当我说没有恢复时,我真的是说这个程序没有必要尝试重做最初导致问题的工作。
【解决方案2】:

在上面的示例中,我认为这是一个好主意,因此您可以控制程序的关闭方式。如果你没有捕捉到这个错误,其他线程可以继续错误地运行(这个错误只在一个线程中抛出)当调用shell可以检查时它也会给出一个退出代码。我会为这个错误使用不同的退出代码。

一般来说,OOME 不保证是可恢复的,但也不保证您的程序也会关闭。

【讨论】:

    【解决方案3】:

    一般规则是:任何异常都应该被最有能力做出充分反应的模块捕获。如果当前方法不知道该做什么,它应该让异常通过,直到到达 main() 或 run() 方法。那些方法不希望有更胜任的方法,所以他们可以捕捉和记录或泛滥。

    【讨论】:

      【解决方案4】:

      捕捉它是很常见的,但只在你的线程中的最高级别。最简单的方法是使用未捕获异常处理程序。这是一个在抛出异常时调用的函数。此时,您可以记录它并告诉用户您退出应用程序的原因。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-10
        • 2011-04-30
        相关资源
        最近更新 更多