试图理解 Java 内存不足错误并捕获可抛出的

Trying to understand the Java Out Of Memory Error and catching the throwable

最近我被要求在我的代码中捕获 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

我们得到了以下输出

运行 测试时间为 6 月 16 日星期三 14:20:31 IST 2021
在 try 块中
在 catch 块内
java.lang.OutOfMemoryError: Java堆space
程序还在运行ning...

运行 测试时间为 6 月 16 日星期三 14:20:36 IST 2021
在 try 块中
在 catch 块内
java.lang.OutOfMemoryError: Java堆space
程序还在运行ning...

运行 测试时间为 6 月 16 日星期三 14:20:41 IST 2021
在 try 块中
在 catch 块内
java.lang.OutOfMemoryError: Java堆space
程序还在运行ning...

运行 测试时间为 6 月 16 日星期三 14:20:46 IST 2021
在 try 块中
在 catch 块内
java.lang.OutOfMemoryError: Java堆space
程序还在运行ning...

运行 测试时间为 6 月 16 日星期三 14:20:51 IST 2021
在 try 块中
在 catch 块内
java.lang.OutOfMemoryError: Java堆space
程序还在运行ning...

为什么这个程序能够运行更进一步,即使它出现内存不足错误。

这是代码的核心:

try {
    Integer[] array = new Integer[10000000 * 10000000];
} catch (Throwable e) {
    /* ... */
}
int arr[] = new int[100];

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

Why this program is able to run further, even it got out of memory error.

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

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

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

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

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

Why this program is able to run further, even it got out of memory error.

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

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


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

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

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

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

当然,也有例外。


还有一些其他事项需要注意。

  1. 您的分配似乎 请求具有 10,000,000 x 10,000,000 个元素的数组。然而,实际情况并非如此。事实上,10000000 * 10000000 是一个 int 表达式。它溢出了,结果是 t运行cated to 276447232。这仍然很大,尤其是因为您需要乘以 8 才能得到大致的数组大小(以字节为单位)。

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

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