繁体   English   中英

JVM 能否在不重启的情况下从 OutOfMemoryError 中恢复

[英]Can the JVM recover from an OutOfMemoryError without a restart

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

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

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

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

  • 确实可能没有足够的内存来执行请求的任务,即使在采取了诸如释放保留内存块之类的恢复步骤之后也是如此。 在这种情况下,您的应用程序可能会陷入循环中,它反复出现恢复,然后再次耗尽内存。

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

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

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

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

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

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

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

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

JVM 会恢复得很好。 但是应用程序服务器和应用程序本身可能会也可能不会恢复,这取决于它们被设计为如何处理这种情况。 (我的经验是,一些应用服务器被设计为解决这个问题,并设计和实施一个复杂的应用程序从OOMEs恢复是很难的,并作适当测试就更难。)

编辑 2

对此评论的回应:

“其他线程可能会等待永远不会到来的通知(等)”真的吗? 被杀死的线程不会解开它的堆栈,在它运行时释放资源,包括持有的锁吗?

对真的! 考虑一下:

线程 #1 运行这个:

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

线程 #2 运行这个:

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

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

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

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

您需要某种机制,借此将线程 #1 的失败(由于 OOME)转变为线程 #2 的线程间通信失败通知。 Erlang 这样做……但不是 Java。 他们可以在 Erlang 中这样做的原因是 Erlang 进程使用严格的类似 CSP 的原语进行通信; 即没有共享数据结构!

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

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

但是,您可以catch它,并在必要时采取替代路径。 try块内的任何分配都将被 GC 处理。

由于 OOME “只是”一个您可以catchError ,我希望不同的 JVM 实现的行为相同。 我至少可以从经验中确认上述情况适用于 Sun JVM。

也可以看看:

我会说这部分取决于导致 OutOfMemoryError 的原因。 如果 JVM 确实内存不足,最好重新启动它,并在可能的情况下使用更多内存(或更高效的应用程序)。 但是,我已经看到相当多的 OOME 是由分配 2GB 阵列等引起的。 在这种情况下,如果它类似于 J2EE Web 应用程序,则错误的影响应该仅限于该特定应用程序,并且 JVM 范围内的重新启动不会有任何好处。

恢复吗? 可能。 任何编写良好的 JVM 只会在尽其所能回收足够的内存来执行您告诉它执行的操作后才会抛出 OOME。 很有可能这意味着您无法恢复。 但...

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

你可能不想依赖这个。 如果您经常收到 OOME,您最好检查一下您的服务器并找出发生了什么以及为什么。 也许您必须清理您的代码(您可能会泄漏或创建过多的临时对象)。 也许您在调用 JVM 时必须提高内存上限。 将 OOME(即使它是可恢复的)视为代码中某个地方出现了一些不好的东西并采取相应行动的标志。 也许您的服务器不必立即停机,但您必须在陷入更深层次的麻烦之前修复某些问题。

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

我不知道不同的 JVM 实现。

只有当垃圾收集器无能为力时,任何正常的 JVM 才会抛出 OutOfMemoryError。 但是,如果您在堆栈帧上足够早地捕获 OutOfMemoryError ,则很可能导致原因本身变得无法访问并被垃圾收集(除非问题不在当前线程中)。

\n

通常,运行其他代码的框架,如应用程序服务器,在面对 OME 时尝试继续是有意义的(只要它可以合理地释放第三方代码),但除此之外,在一般情况下,恢复可能应该包括保释并告诉用户原因,而不是试图继续,好像什么也没发生。

回答您新更新的问题:如果一切正常,没有理由认为您需要关闭服务器。 我对 JBoss 的经验是,只要 OME 不影响部署,一切都会正常进行。 如果您进行大量热部署,有时 JBoss 会耗尽 permgen 空间。 那么确实情况是没有希望的,立即重启(这将不得不用杀戮来强制)是不可避免的。

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

你不能完全是一个有 OutOfMemoryError 的 JVM。 至少对于 oracle JVM,您可以添加-XX:OnOutOfMemoryError="cmd args;cmd args"并采取恢复操作,例如杀死 JVM 或将事件发送到某个地方。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM