试图理解 Java Out Of Memory 错误并捕获 throwable

Posted

技术标签:

【中文标题】试图理解 Java Out Of Memory 错误并捕获 throwable【英文标题】:Trying to understand the Java Out Of Memory Error and catching the throwable 【发布时间】:2021-06-16 09:15:44 【问题描述】:

最近我被要求在我的代码中捕获 Throwable。 所以我们遇到了一个我们应该做还是不做的论点,我举了一个 OutOfMemoryError 的例子,在这种情况下,即使我们发现错误,我们的代码也不会被进一步处理。

所以为了测试这个理论,我们为它创建了示例代码。

public class TestErrorInThread 

public static void test() 
    System.out.println("Running the test at time " + new Date());
    try 
        System.out.println("Inside try block");
        Integer[] array = new Integer[10000000 * 10000000];
     catch (Throwable e) 
        System.out.println("Inside catch block");
        System.out.println(e);
    
    int arr[] = new int[100];
    System.out.println("Programme is still running...");



public static void main(String[] args) 
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    Runnable runnable = TestErrorInThread::test;
    scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 5, TimeUnit.SECONDS);

要运行我们使用以下命令的代码。

java -Xmx1m TestErrorInThread

我们得到了以下输出

在 IST 2021 年 6 月 16 日星期三 14:20:31 时间运行测试 内部尝试块 内部捕获块 java.lang.OutOfMemoryError:Java 堆空间 程序仍在运行...

在 IST 2021 年 6 月 16 日星期三 14:20:36 时间运行测试 内部尝试块 内部捕获块 java.lang.OutOfMemoryError:Java 堆空间 程序仍在运行...

在 IST 2021 年 6 月 16 日星期三 14:20:41 时间运行测试 内部尝试块 内部捕获块 java.lang.OutOfMemoryError:Java 堆空间 程序仍在运行...

在 IST 2021 年 6 月 16 日星期三 14:20:46 时间运行测试 内部尝试块 内部捕获块 java.lang.OutOfMemoryError:Java 堆空间 程序仍在运行...

在 IST 2021 年 6 月 16 日星期三 14:20:51 运行测试 内部尝试块 内部捕获块 java.lang.OutOfMemoryError:Java 堆空间 程序仍在运行...

为什么这个程序能够继续运行,即使它出现了内存不足的错误。

【问题讨论】:

这能回答你的问题吗? Behavior of a Java process in case of OutOfMemoryError 程序的输出与理论不符,这里为什么JVM没有crash。 【参考方案1】:

这是代码的核心:

try 
    Integer[] array = new Integer[10000000 * 10000000];
 catch (Throwable e) 
    /* ... */

int arr[] = new int[100];

根据您问题中的输出,这在大分配中失败,在小分配中恢复并成功。

为什么这个程序能够继续运行,即使它出现了内存不足的错误。

简短的回答:因为 JVM 的设计使其可以在大多数情况下从 OOME 中恢复。

在您的示例中,第一个 new 运算符将导致对大量内存的内存分配请求(见下文)。内存分配器会注意到请求大于可用的。然后(通常)会触发完整的垃圾收集器以释放尽可能多的内存,然后重复请求。

在此示例中,仍然没有足够的内存用于非常大的分配。然后分配器抛出一个OutOfMemoryError,然后像任何其他异常一样传播。在这个时间点,会有很多可用内存......但对于巨大的分配来说还不够。

然后您的代码会捕获 OOME,并尝试分配一个小得多的数组。成功了,因为内存可用。

您的测试应用程序重复执行此序列,并且每次都表现相同。

为什么这个程序能够继续运行,即使它出现了内存不足的错误。

在您的示例中,JVM 仅针对巨大的分配请求“内存不足”。对于较小的请求,没有问题。

请注意,巨大的new 并没有实际上分配内存。它测试了它是否可以进行分配,并确定它不能


基本上,您的“理论”是不正确的。然而,通常尝试捕获并从 OOME 中恢复是一个坏主意。以下是一些原因:

    OOME 容易导致线程意外终止,使数据结构处于不一致的状态,通知将无法发送等等。恢复可能会有问题。

    如果恢复导致重复相同的请求,您很可能会重复 OOME。

    如果 OOME 的根本原因是内存泄漏,那么从 OOME 中恢复不太可能修复内存泄漏。因此,恢复会导致 OOME 频率增加,直到系统停止运行。

当然,上述所有情况都有例外。


还有几点需要注意。

    您的分配似乎正在请求一个包含 10,000,000 x 10,000,000 个元素的数组。然而,实际情况并非如此。事实上,10000000 * 10000000 是一个int 表达式。它溢出,结果被截断为276447232。这仍然非常大,尤其是因为您需要乘以 8 才能获得以字节为单位的近似数组大小。

    由于您的 JVM 的最大堆大小仅为 1MB,因此分配器可能不会费心运行 GC。这对结果没有任何影响。但是,如果 GC 有可能释放足够的内存,您可以确保它在分配器放弃并引发 OOME 之前运行。

    JVM 的内存管理器将堆分成多个空间。详细信息取决于您选择的 GC。当请求一个非常大的对象时,分配器必须找到适合一个空间的连续内存区域。这可能是不可能的......即使总可用空间应该足够大。

【讨论】:

感谢@Stephen C 的回答,我能够理解OOME 的概念。你能给我推荐一本我可以了解这些概念的书吗? 我对你没有任何建议。 好的,只是想了解另一件事,如果我删除 try-catch 块,它不会继续运行,它只是在初始化时被挂起,终端上没有打印错误。 是的。这些任务正在抛出一个正在杀死执行程序线程的 OOME。由于您没有设置defaultUncaughtExceptionHandler,线程上未捕获的异常(“main”除外)将被静默忽略。执行器服务将为终止的执行器线程创建一个替换。 感谢斯蒂芬的回复。

以上是关于试图理解 Java Out Of Memory 错误并捕获 throwable的主要内容,如果未能解决你的问题,请参考以下文章

Out of memory: Kill process 内存不足

webpack打包---报错内存溢出javaScript heap out of memory

vue项目编译时报错:FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

vue项目编译时报错:FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

win10系统出现out of memory怎么办

PHP运行错最有效解决办法Fatal error: Out of memory (allocated 786432) (tried to allocate 98304 bytes) in H:fre(