JVM是否可以在不重新启动的情况下从OutOfMemoryError中恢复

时间:2010-06-17 00:45:54

标签: java jvm out-of-memory

  1. 如果在更多对象分配请求进入之前有机会运行GC,JVM是否可以在没有重启的情况下从OutOfMemoryError中恢复?

  2. 各方面的JVM实现在这方面有所不同吗?

  3. 我的问题是关于JVM恢复而不是用户程序试图通过捕获错误来恢复。换句话说,如果在应用程序服务器(jboss / websphere / ..)中抛出OOME,我重启吗?或者,如果进一步的请求似乎没有问题,我可以让它运行。

7 个答案:

答案 0 :(得分:42)

它可能有效,但通常是一个坏主意。无法保证您的应用程序成功恢复,或者它将知道它是否未成功。例如:

  • 即使在执行恢复步骤(例如释放保留内存块)之后,也可能没有足够的内存来执行请求的任务。在这种情况下,您的应用程序可能会陷入一个循环,在该循环中它会反复出现恢复状态,然后再次耗尽内存。

  • OOME可能会被抛出任何线程。如果应用程序线程或库不是为处理它而设计的,这可能会使一些长期存在的数据结构处于不完整或不一致的状态。

  • 如果线程因OOME而死亡,则应用程序可能需要在OOME恢复过程中重新启动它们。至少,这使应用程序更加复杂。

  • 假设线程使用notify / wait或某些更高级别的机制与其他线程同步。如果该线程从OOME死掉,其他线程可能会等待通知(等)永远不会来......例如。为此设计可能会使应用程序变得更加复杂。

总之,设计,实现和测试从OOME恢复的应用程序可能很困难,尤其是当应用程序(或运行它的框架或它使用的任何库)是多线程的时候。将OOME视为致命错误是一个更好的主意。

另见my answer相关问题:

编辑 - 回应此后续问题:

  

换句话说,如果在应用程序服务器(jboss / websphere / ..)中抛出OOME,我是否必须重新启动它?

不,你不得重启。但它可能明智,特别是如果您没有良好/自动的方式来检查服务是否正常运行。

JVM恢复得很好。但是,应用程序服务器和应用程序本身可能会或可能不会恢复,具体取决于它们设计为应对这种情况的程度。 (我的经验是,某些应用程序服务器旨在解决这个问题,而设计和实现复杂的应用程序以便从OOME恢复很难,并且正确测试它更难。)

编辑2

回应此评论:

  

“其他线程可能会等待通知(等)永远不会来”真的吗?被杀死的线程不会展开它的堆栈,释放资源,包括保持锁定吗?

是的,真的!考虑一下:

线程#1运行:

    synchronized(lock) {
         while (!someCondition) {
             lock.wait();
         }
    }
    // ...

线程#2运行:

    synchronized(lock) {
         // do stuff
         lock.notify();
    }

如果线程#1正在等待通知,并且线程#2在// do something部分中获得OOME,则线程#2将不会进行notify()调用,并且线程#1可能永远陷入困境,等待永远不会发生的通知。当然,线程#2保证释放lock对象上的互斥锁......但这还不够!

  

如果不是线程运行的代码不是异常安全的,这是一个更普遍的问题。

“异常安全”不是我听说过的一个术语(虽然我知道你的意思)。 Java程序通常不会设计为对意外异常具有弹性。实际上,在如上所述的场景中,很可能介于很难和不可能使应用程序异常安全。

你需要一些机制,使得线程#1(由于OOME)的失败变成线程#2的线程间通信失败通知。 Erlang这样做......但不是Java。他们在Erlang中可以做到这一点的原因是Erlang进程使用严格的类似CSP的原语进行通信;即没有共享数据结构!

(请注意,您可以针对任何意外异常...而不仅仅是Error异常获得上述问题。某些类型的Java代码尝试从意外的异常很可能会以严重的方式结束。)

答案 1 :(得分:3)

当JVM 位于OutOfMemoryError的边缘时,运行GC。如果GC根本没有帮助,那么JVM将抛出OOME。

可以然而catch它,如果有必要,可以采取替代路径。 <{1}}块内的任何分配都将被GC。

由于OOME只是try,你只能Error,我希望不同的JVM实现行为相同。我至少可以从经验中证实,上面的内容适用于Sun JVM。

另见:

答案 2 :(得分:3)

我会说这部分取决于导致OutOfMemoryError的原因。如果JVM真的在内存上运行不足,那么重新启动它可能是一个好主意,如果可能的话,可以使用更多内存(或者更高效的应用程序)。但是,我看到相当数量的OOME是由分配2GB阵列等造成的。在这种情况下,如果它类似于J2EE Web应用程序,则错误的影响应该限制在该特定应用程序中,并且JVM范围的重新启动不会有任何好处。

答案 3 :(得分:2)

可以恢复吗?有可能。任何写得很好的JVM只会在尝试了所有可以回收足够内存以完成你告诉它的事情之后抛出一个OOME。这很有可能意味着你无法恢复。但...

这取决于很多事情。例如,如果垃圾收集器不是复制收集器,则“内存不足”情况实际上可能是“没有足够大的块来分配”。展开堆栈的行为可能会在以后的GC轮次中清理对象,从而为您的目的留下足够大的开放块。在那种情况下,您可以重新启动。因此,至少重试一次可能是值得的。但...

你可能不想依赖于此。如果你有任何规律性的OOME,你最好查看你的服务器,看看发生了什么,为什么。也许你必须清理你的代码(你可能会泄漏或制造太多的临时对象)。也许你必须在调用JVM时提高内存上限。对OOME进行处理,即使它是可以恢复的,也可以作为一个标志,表明在你的代码中某些地方出现了一些不好的事情,并采取相应的行动。也许你的服务器不必在NOWNOWNOWNOW下来,但你必须在遇到更深的麻烦之前解决问题。

答案 4 :(得分:1)

您可以增加从这种情况中恢复的几率,尽管不建议您尝试。你所做的是在启动时预先分配一些固定数量的内存,专门用于恢复工作,当你捕获OOM时,将预先分配的引用清空,你可能更多有一些内存可用于你的恢复序列。

我不知道不同的JVM实现。

答案 5 :(得分:1)

只有垃圾收集器无法执行任何操作时,任何理智的JVM都会抛出OutOfMemoryError。但是,如果你在堆栈帧上足够早地捕获OutOfMemoryError,那么原因本身就变得无法访问并且被垃圾收集(除非问题不在当前线程中)。

<罢工>

通常运行其他代码的框架(如应用程序服务器)试图继续面对OME是有意义的(只要它可以合理地释放第三方代码),否则,在一般情况下,恢复应该包括捞出并告诉用户原因,而不是试图继续进行,就像没有发生任何事情一样。

回答您新近更新的问题:如果一切正常,您没有理由认为需要关闭服务器。我对JBoss的经验是,只要OME不影响部署,就可以正常工作。如果您进行大量热部署,JBoss有时会耗尽permgen空间。事实上情况是没有希望的,立即重启(必须用杀戮强迫)是不可避免的。

当然,每个应用服务器(和部署方案)都会有所不同,这实际上是从每种情况下的经验中学到的东西。

答案 6 :(得分:0)

您不能完全排除具有OutOfMemoryError的JVM。至少使用oracle JVM,您可以添加-XX:OnOutOfMemoryError="cmd args;cmd args"并执行恢复操作,例如杀死JVM或将事件发送到某个地方。

参考:https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html