JVM 能否在不重新启动的情况下从内存外错误中恢复

2022-08-31 20:17:49
  1. 如果 JVM 有机会在更多对象分配请求传入之前运行 GC,它是否可以在不重新启动的情况下从 中恢复?OutOfMemoryError

  2. 各种JVM实现在这方面是否不同?

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


答案 1

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

  • 确实可能没有足够的内存来执行请求的任务,即使在采取恢复步骤(如释放保留内存块)之后也是如此。在此情况下,您的应用程序可能会卡在循环中,反复显示为恢复,然后再次耗尽内存。

  • OOME 可以扔到任何线程上。如果应用程序线程或库的设计不是为了应对它而设计的,这可能会使某些长期数据结构处于不完整或不一致的状态。

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

  • 假设一个线程使用通知/等待或某种更高级别的机制与其他线程同步。如果该线程从OOME死亡,则其他线程可能会等待永远不会到来的通知(等)...例如。为此进行设计可能会使应用程序明显更加复杂。

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

另请参阅我对相关问题的回答:

编辑 - 回应这个后续问题:

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

不,您不必重新启动。但这可能是明智的,特别是如果你没有一个好的/自动化的方法来检查服务是否正常运行。

JVM将恢复得很好。但是,应用程序服务器和应用程序本身可能会恢复,也可能不会恢复,这取决于它们在设计上如何应对这种情况。(我的经验是,一些应用程序服务器不是为应对这种情况而设计的,并且设计和实现复杂的应用程序以从OOME中恢复是困难的,并且正确测试它甚至更难。

编辑 2

针对这一评论:

“其他线程可能会等待从未到来的通知(等)”真?难道被杀死的线程不会展开其堆栈,随心所欲地释放资源,包括持有的锁吗?

是的,真的!请考虑以下情况:

线程 #1 运行此内容:

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

线程 #2 运行以下命令:

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

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

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

“异常安全”不是我听说过的术语(尽管我知道你的意思)。Java 程序通常不是为能够灵活应对意外异常而设计的。实际上,在上述情况下,使应用程序异常安全可能介于困难和不可能之间。

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

(请注意,对于几乎任何意外的异常,您都可能遇到上述问题...不仅仅是例外。在某些类型的 Java 代码中,尝试从意外异常中恢复可能会以糟糕的结局告终。Error


答案 2

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

但是,您可以这样做,并在必要时采取其他路径。区块内的任何分配都将被GC'ed。catchtry

由于OOME是“只是”一个你可以只是,我希望不同的JVM实现表现相同。我至少可以从经验中确认,对于 Sun JVM 来说,上述情况是正确的。Errorcatch

另请参阅:


推荐