JVM能否从OutOfMemoryError恢复而不重启

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

  2. 各种JVM实现在这方面有什么不同?

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

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

  • 即使在执行释放保留内存块之类的恢复步骤之后,确实可能没有足够的内存来执行所请求的任务。 在这种情况下,您的应用程序可能会卡在一个循环中,它会重复出现,然后再次耗尽内存。

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

  • 如果线程由于OOME而死亡,应用程序可能需要重新启动它们作为OOME恢复的一部分。 这至less会使应用程序更复杂。

  • 假设一个线程使用通知/等待或一些更高级别的机制与其他线程同步。 如果该线程死于OOME,其他线程可能会等待通知(等),永远不会来…例如。 为此devise可能会使应用程序显得更加复杂。

总之,devise,实现和testing应用程序以从OOME中恢复可能是困难的,特别是如果应用程序(或其运行的框架,或它使用的任何库)是multithreading的。 把OOME当成一个致命的错误是个好主意。

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

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

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

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

JVM将恢复正常。 但是,应用程序服务器和应用程序本身可能会恢复,也可能不会恢复,这取决于应用程序服务器和应用程序的devise有多好。 (我的经验是,一些应用程序服务器并不是为了应付这种情况而devise的,devise和实现一个复杂的应用程序来从OOME恢复是很困难的,而且正确地testing更加困难。

编辑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对象上的互斥lock ……但这还不够!

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

“exception安全”不是我听说过的术语(虽然我知道你的意思)。 Java程序通常不会被devise成能够抵御意外的exception。 事实上,在像上面这样的情况下,它可能会在难以或不可能的情况下使应用程序exception安全。

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

(请注意,对于任何意外的exception,您都可以得到上述问题……不仅仅是Errorexception,有些Java代码试图从意外的exception中恢复可能会很糟糕。

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

可以 catch它,如果有必要的话可以select另一条path。 try块内的任何分配都将被GC化。

由于OOME是“只是”一个你可以catch到的Error ,我期望不同的JVM实现行为相同。 我至less可以从经验中证实,对于Sun JVM,上述情况是正确的。

也可以看看:

  • 捕获java.lang.OutOfMemoryError
  • 有没有可能在java中捕捉到内存exception?

我会说,这部分取决于是什么导致了OutOfMemoryError。 如果JVM确实内存不足,重新启动它可能是一个好主意,如果可能的话(或更高效的应用程序)使用更多的内存。 但是,我看到了相当数量的分配2GB数组等引起的OOME。 在这种情况下,如果它是J2EE Web应用程序,那么错误的影响应该限制在特定的应用程序中,而JVM范围内的重新启动不会有任何好处。

它能恢复吗? 有可能。 任何编写良好的JVM只会在尝试尽可能多地回收足够的内存来完成你所要做的事情之后才会抛出一个OOME。 这很有可能意味着你无法恢复。 但…

这取决于很多东西。 例如,如果垃圾收集器不是一个复制收集器,“内存不足”的条件可能实际上是“没有足够的剩余空间来分配”。 解开堆栈的行为可能会在后续的GC回合中清理出对象,这些对象留下足够大的空间用于您的目的。 在这种情况下,您可能会重新启动。 至less重试一次可能是值得的。 但…

你可能不想依靠这个。 如果你有一个规律性的OOME,你最好看看你的服务器,并找出发生了什么事情,为什么。 也许你必须清理你的代码(你可能会泄漏或制造太多的临时对象)。 也许你在调用JVM时不得不提高内存的上限。 对待OOME,即使它是可恢复的,作为一个迹象表明某些不好的东西已经打击了你的代码中的粉丝,并相应地采取行动。 也许你的服务器不需要NOWNOWNOWNOWNOW,但是在你陷入更深的麻烦之前,你将不得不解决一些问题。

尽pipe不build议您尝试,但您可以增加从此scheme恢复的可能性。 你所做的是在启动时预先分配一些固定数量的内存来完成你的恢复工作,当你捕捉到OOM时,将预分配的引用清空,并且你更有可能在你的内存中使用一些内存恢复顺序。

我不知道不同的JVM实现。

只有当垃圾收集器没有任何东西可以做时,任何理智的JVM都会抛出一个OutOfMemoryError。 但是,如果您在堆栈框架上足够早地捕获OutOfMemoryError,则原因本身变得无法访问并被垃圾收集(除非问题不在当前线程中)。

一般来说,运行其他代码的框架(如应用程序服务器)试图继续面向OME是有意义的(只要它能合理地释放第三方代码),否则在一般情况下,恢复应该可能由保释和告诉用户为什么,而不是像没有任何事情那样继续下去。

回答你最近更新的问题:没有理由认为你需要closures服务器,如果一切正常。 我对JBoss的经验是,只要OME不影响部署,事情就可以正常工作。 如果你做了很多热部署的话,有时候JBoss会耗尽permgen空间。 那么情况确实是无望的,立即重启(这将不得不被迫杀死)是不可避免的。

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