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

sen*_*ngs 43 java jvm out-of-memory

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

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

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

Ste*_*n C 44

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

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

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

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

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

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

另见对相关问题的回答:

编辑 - 回应此后续问题:

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

不,你不必须重新启动.但是,这可能是明智的,特别是如果您没有良好/自动的方式来检查服务是否正确运行.

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

编辑2

回应此评论:

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

对真的!考虑一下:

线程#1运行:

    synchronized(lock) {
         while (!someCondition) {
             lock.wait();
         }
    }
    // ...
Run Code Online (Sandbox Code Playgroud)

线程#2运行:

    synchronized(lock) {
         // do stuff
         lock.notify();
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

(请注意,您可以针对任何意外异常获得上述问题...而不仅仅是Error异常.在某些类型的Java代码中,尝试从意外异常中恢复可能会很糟糕.)

  • @SpaceTrucker - 区别在于其他异常不会自发发生。它们的发生是由于错误或在某种程度上可预测的某些情况造成的。OTOH、OOME 和其他错误可能会自发发生!在实践中,无法预测内存不足的地点/时间。但谁或什么“有错”并不重要。问题是,这种情况会导致 OOME 的恢复出现问题。 (2认同)