试图理解 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
PHP运行错最有效解决办法Fatal error: Out of memory (allocated 786432) (tried to allocate 98304 bytes) in H:fre(