为什么JVM可以自己从OOM Java堆空间中恢复


Integer[][] data = new Integer[1000000][100000];

就像上面简单的演示代码一样,我尝试在pandora容器(它是阿里巴巴开发的web容器,像tomcat)中应用显著的大内存和触发OOM。但是这个错误似乎只影响当前的请求,web服务不会崩溃;据我所知,与异常不同,java中的错误是不应该恢复的,并且会影响整个过程。我不明白,请指点一下……谢谢。

第一个:

据我所知,与异常不同,java中的错误是不应该恢复的,并且会影响整个过程。

一般来说这是正确的。然而,在web容器的情况下,从请求线程上的OOME恢复比典型的多线程应用程序有更好的成功机会。

为什么?

因为在一个工作线程上处理一个web请求所做的工作通常是独立于其他线程的。这意味着请求中的OOME不太可能离开共享数据结构不一致。

但是您仍然有问题,OOME的根本原因可能是内存泄漏…这最有可能不会消失当web容器清理请求线程并创建一个新的。因此,web容器从oome中恢复仍然是可疑的。

但这是相当常见的行为。我认为原因是,在合理的机会下尝试恢复比快速失败要好。


那么为什么恢复是可能的呢?

考虑下面的代码片段:

public void test() {
try {
Integer[][] data = new Integer[1000000][100000];
} catch (OutOfMemoryError ex) {
// log it
}
// do something else
}

观察:

  1. 在GC运行后发生OOME。导致OOME的典型JVM操作序列如下所示:

    • 尝试分配大对象
    • 查找空间不足
    • 运行一个新的空间GC
    • 仍然没有足够的可用空间
    • 运行完整GC
    • 仍然没有足够的可用空间
    • <
    • 扔OOME/gh>
  2. new Integer[10000][10000]是一个全有或全无的东西。如果它触发OOME,那么到目前为止它分配的所有对象都将不可访问。因此,如果// do something else代码试图分配另一个对象,堆仍然是满的,那么JVM将再次运行GC…它将收回那些无法到达的物体…我们又重新开始了。

  3. 即使不是…当data超出作用域时,它所指向的Integer[][]Integer[]对象树现在可能被GC检测为不可达。

  4. 如果在子线程上抛出OOME,并且允许线程死亡,则线程的所有局部变量将不再可访问。这会导致更多(潜在的)不可访问的对象。

问题在于,当您从OOME捕获并恢复时,可能存在一些可收集的垃圾。


那么如果可以恢复,为什么人们建议反对从OOMEs中恢复过来?

  1. 因为OOME的原因可能是内存泄漏。恢复一个内存溢出,造成内存泄漏会导致低劣的性能。堆最终会被填满,导致GC花费太多时间。

  2. 因为OOME可能导致数据结构处于不一致状态;例如,当它得到OOME时,你的代码正在更新它。

  3. 因为OOME可以破坏并发行为。例如,假设线程A正在等待线程B的notify。如果B得到一个OOME,它可能会完全死亡,或者它可能会尝试恢复…在它的锁被释放的某个点上。无论哪种方式,都存在线程a将永远等待永远不会发生的通知的风险。(线程B应该触发应用程序关闭以避免这种情况。)

错误OutOfMemoryError: Java heap space类似于任何其他异常,它只导致当前线程被终止。这只是关于耗尽Java堆空间。因此,如果线程被终止,并且在该线程中创建的所有对象变得不可访问并且可以被收集,则有足够的堆空间可以继续,并且没有理由导致整个应用程序崩溃。

应用程序本身只有在超出操作系统所能提供的内存时才会被操作系统杀死。然而,在某些jvm中,您可以使用一些键来显式地在任何内存不足错误时崩溃,例如-XX:+CrashOnOutOfMemoryError

最新更新