【问题标题】:Can the JVM recover from an OutOfMemoryError without a restartJVM 可以在不重新启动的情况下从 OutOfMemoryError 中恢复吗
【发布时间】:2011-03-04 17:53:38
【问题描述】:
  1. 如果 JVM 有机会在更多对象分配请求到来之前运行 GC,它能否在不重新启动的情况下从 OutOfMemoryError 中恢复?

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

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

【问题讨论】:

标签: java jvm out-of-memory


【解决方案1】:

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

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

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

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

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

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

另请参阅my answer 相关问题:

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

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

不,您不必必须重新启动。但这可能是明智的,尤其是如果您没有一种好的/自动化的方式来检查服务是否正常运行。

JVM 将恢复正常。但是应用程序服务器和应用程序本身可能会或可能不会恢复,这取决于它们设计用于应对这种情况的程度。 (我的经验是,一些应用程序服务器不是为了解决这个问题而设计的,设计和实现一个复杂的应用程序来从 OOME 中恢复是很困难的,正确地测试它就更难了。)

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

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

“异常安全”不是我听说过的术语(尽管我知道你的意思)。 Java 程序通常不会被设计为对意外异常具有弹性。确实,在上述情况下,要使应用程序异常安全,可能很难甚至不可能。

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

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

【讨论】:

  • “其他线程可能会等待通知(等)永远不会到来”真的吗?被杀死的线程不会展开它的堆栈,释放资源,包括持有的锁吗?如果不是,线程运行的代码不是异常安全的,这是一个更普遍的问题。
  • “OOME 可能在任何线程上抛出”并且在任何时候,而不仅仅是new。这包括使您的程序处于不一致状态的位置。见stackoverflow.com/questions/8728866/…
  • 对于您的编辑 2,我倾向于说 lock.wait(); 是问题所在,而不是 OOME 本身,因为任何运行时异常都可能导致相同的行为。
  • @SpaceTrucker - 不同之处在于其他异常不会自发发生。它们是由于错误或在某种程度上可预测的某些条件而发生的。 OTOH、OOME 和其他错误可能会自发发生!无法预测您在哪里/何时耗尽内存......在实践中。但实际上谁或什么是“过错”并不重要。问题是这种事情会让 OOME 的问题恢复。
  • 我想可以仔细设计和测试一个应用程序以在 OOM 后保持状态一致。但是,如果您使用任何第三方库,那么您永远不会知道。看看这个:issues.apache.org/jira/browse/HTTPCLIENT-2039
【解决方案2】:

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

可以但是catch它,如果有必要采取替代路径。 try 块内的任何分配都将被 GC'ed。

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

另请参阅:

【讨论】:

  • 我建议将它 high 放在堆栈上。即在任务开始时。这样,它可以尝试 gc 任何在内存不足时“在范围内”的数据。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

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

    你可能不想依赖这个。如果您经常遇到 OOME,最好查看您的服务器并找出发生了什么以及原因。也许你必须清理你的代码(你可能会泄漏或制作太多临时对象)。调用 JVM 时,您可能必须提高内存上限。将 OOME(即使它是可恢复的)视为代码中某处发生了坏事并采取相应措施的迹象。也许您的服务器不必立即关闭,但您必须在遇到更严重的问题之前修复一些问题。

    【讨论】:

      【解决方案5】:

      虽然不建议您尝试,但您可以增加从这种情况中恢复的几率。您所做的是在启动时预先分配一些固定数量的内存,专门用于进行恢复工作,当您捕获 OOM 时,取消该预先分配的引用,您更多可能会有一些内存可用于您的恢复序列。

      我不知道不同的 JVM 实现。

      【讨论】:

        【解决方案6】:

        只有在垃圾收集器无能为力的情况下,任何理智的 JVM 都会抛出 OutOfMemoryError。但是,如果您在堆栈帧上及早捕获 OutOfMemoryError,则很可能原因本身变得无法访问并且已被垃圾收集(除非问题不在当前线程中)。

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

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

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

        【讨论】:

        • “一般来说,运行其他代码的框架(如应用程序服务器)尝试在 OME 面前继续运行是有意义的”。我不同意,除非框架非常健壮,否则尝试从 OOME 中恢复可能会导致(例如)紧张的服务器。去过那里,看到了!
        • @Stephen C,想到在 JBoss 中的任何 OME 上调用 System.exit(1) 会是什么样子,我不寒而栗。每次用户厌倦阅读太多数据时,每个人都会感到沮丧。我同意这可能会导致问题,但应用程序服务器的 OME 最可能的原因是用户代码做的太多,只要他们在用户代码分配不再可访问的点上捕获它,完全-恢复是最有可能的结果,值得编码,IMO。
        • @Yishai - 一个错误的请求(例如用户试图读取太多数据)首先不应该被允许导致 OOME。正确的解决方法是使请求处理更具防御性……而不是尝试从 OOME 中恢复。
        • @Stephen C,应用服务器的作者没有这个选项。
        • @Yishai - 是的,他/她有。只需为 ding-bat 应用程序开发人员/部署人员提供一种方法来启用狡猾的 OOME 恢复。
        【解决方案7】:

        您不能完全使用具有 OutOfMemoryError 的 JVM。至少使用 oracle JVM,您可以添加 -XX:OnOutOfMemoryError="cmd args;cmd args" 并采取恢复操作,例如终止 JVM 或将事件发送到某处。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-11-11
          • 2011-04-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多