java - 分配大于堆的数组时出现意外的 OutOfMemoryError
问题描述
今天我在玩OOM错误,我发现了一些我自己无法解释的东西。
我尝试分配一个大于堆的数组,期待“请求的数组大小超过 VM 限制”错误,但我得到一个“ Java 堆空间”错误。
根据JDK 11 文档“3 Troubleshoot Memory Leaks > Understanding the OutOfMemoryError Exception”:
线程 thread_name 中的异常:java.lang.OutOfMemoryError:请求的数组大小超过 VM 限制
原因:详细消息“请求的数组大小超过 VM 限制”表示应用程序(或该应用程序使用的 API)试图分配一个大于堆大小的数组。例如,如果应用程序尝试分配 512 MB 的数组,但最大堆大小为 256 MB,则会抛出 OutOfMemoryError,原因是“请求的数组大小超过 VM 限制”。
代码 :
public class MemOverflow {
public static void main(final String[] args) {
System.out.println("Heap max size: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");
long[] array = new long[100_000_000]; // ~800MB
System.out.println(array.length);
}
}
此代码按预期工作,堆足够大以存储我的数组:
$ javac MemOverflow.java && java -Xmx800m MemOverflow
Heap max size: 800MB
100000000
但是,当我减小堆大小时,会出现错误的 OOM 错误:
$ javac MemOverflow.java && java -Xmx100m MemOverflow
Heap max size: 100MB
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at MemOverflow.main(MemOverflow.java:5)
# I'm expected the "Requested array size exceeds VM limit" error here
我在这里错过了什么吗?我知道我可以生成我想要Integer.MAX_VALUE
用作数组大小的错误,但我希望通过调整堆大小来抛出它。
Java版本:
openjdk version "11.0.8" 2020-07-14
OpenJDK Runtime Environment (build 11.0.8+10-post-Ubuntu-0ubuntu120.04)
OpenJDK 64-Bit Server VM (build 11.0.8+10-post-Ubuntu-0ubuntu120.04, mixed mode)
编辑: 基于@cdalxndr 的回答,我也尝试使用 Oracle 最新的 JDK 15,但我得到了相同的结果。
java version "15" 2020-09-15
Java(TM) SE Runtime Environment (build 15+36-1562)
Java HotSpot(TM) 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)
编辑 2: 我报告了它:JDK-8254804。
编辑 3:2021-01-19 更新
文档已修复。在 v16-ea(早期访问)中。
线程 thread_name 中的异常:java.lang.OutOfMemoryError:请求的数组大小超过 VM 限制
原因:详细消息“请求的数组大小超过 VM 限制”表示应用程序(或该应用程序使用的 API)试图分配一个大小大于 JVM 实现限制的数组,而不管可用的堆大小是多少。
编辑 4:2021-03-20 更新
解决方案
引用的文档,了解 OutOfMemoryException,
线程 thread_name 中的异常:java.lang.OutOfMemoryError:请求的数组大小超过 VM 限制
原因:详细消息“请求的数组大小超过 VM 限制”表明应用程序(或该应用程序使用的 API)试图分配大于堆大小的数组。例如,如果应用程序尝试分配 512 MB 的数组,但最大堆大小为 256 MB,则会抛出 OutOfMemoryError,原因是“请求的数组大小超过 VM 限制”。
行动:通常问题要么是配置问题(堆大小太小),要么是导致应用程序尝试创建巨大数组的错误(例如,当数组中的元素数量是使用计算尺寸不正确)。
...是不正确的。
“请求的数组大小超过 VM 限制”消息的真正含义是 JVM 的实现施加了限制。超过这个限制总是会导致失败,不管有多少可用的堆空间。Integer.MAX_VALUE
如果尝试分配具有接近元素的数组,则会发生带有此消息的 OutOfMemoryError 。(这不是一个固定的限制。见下文。)
相比之下,带有“Java 堆空间”消息的 OutOfMemoryError 意味着如果情况不同,请求可能已经完成:例如,如果其他对象使用的内存更少,或者 JVM 的最大堆大小增加了。
我重新利用了错误JDK-8254804来修复有问题的文档。
ArraysSupport.java中有一条似乎相关的评论:
/**
* The maximum length of array to allocate (unless necessary).
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* {@code OutOfMemoryError: Requested array size exceeds VM limit}
*/
public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 8;
请注意,这是在库代码中,而不是在 Hotspot JVM 代码中。这是库代码的保守猜测对于不超过 JVM 可能施加的限制的数组大小。问题是 JVM 的最大数组大小限制可能会根据不同的情况而有所不同,例如使用的垃圾收集器,JVM 是在 32 位还是 64 位模式下运行,甚至是 JVM(Hotspot 或其他)它正在运行。这个库在这里需要有点保守,因为它需要在不同的情况下在不同的 JVM 上运行,并且没有返回“最大允许数组大小”的 JVM 的标准化接口。(实际上,这样的概念可能定义不明确,因为不同数组类型的最大数组大小可能不同,或者它可能会从一个时刻到下一个时刻发生变化。)