意外的内存不足分配大于堆的数组时出错

2022-09-04 02:18:28

我今天正在玩OOM错误,我发现了一些我无法解释自己的东西。

我尝试分配一个大于堆的数组,期望出现“请求的数组大小超过 VM 限制”错误,但收到“Java 堆空间”错误。

根据 JDK 11 文档“3 解决内存泄漏>了解内存不足错误异常”

线程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更新

JDK 16 现已随固定文档一起发布


答案 1

引用的文档,了解 OutOfMemoryException

线程thread_name中的异常:java.lang.OutOfMemoryError: 请求的数组大小超过 VM 限制

原因:详细信息消息“请求的数组大小超过 VM 限制”表示应用程序(或该应用程序使用的 API)试图分配大于堆大小的数组。例如,如果应用程序尝试分配 512 MB 的数组,但最大堆大小为 256 MB,则将引发 OutOfMemoryError,理由是“请求的数组大小超过 VM 限制”。

行动:通常,问题要么是配置问题(堆大小太小),要么是导致应用程序尝试创建大型数组的错误(例如,当使用计算错误大小的算法计算数组中的元素数时)。

...不正确。

“请求的数组大小超过 VM 限制”消息的真正含义是 JVM 的实现施加了限制。超过此限制将始终导致故障,无论有多少堆空间可用。如果尝试分配具有接近元素的数组,则会发生带有此消息的 OutOfMemor 错误。(这不是一个固定的限制。见下文。Integer.MAX_VALUE

相比之下,带有“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;

请注意,这是在库代码中,而不是在热点JVM代码中。这是库代码对数组大小的保守猜测,该数组大小不超过 JVM 可能施加的限制。问题在于,JVM 的最大数组大小限制可能因不同的情况而异,例如正在使用的垃圾回收器、JVM 是在 32 位还是 64 位模式下运行,甚至是运行它的 JVM(热点或其他)。库在这里需要有点保守,因为它需要在不同情况下在不同的JVM上运行,并且没有返回“最大允许数组大小”的JVM的标准化接口。(实际上,这样的概念可能定义不清,因为不同数组类型的最大数组大小可能不同,或者它可能会从一个时刻变化到下一个时刻。


答案 2

这可能是 jdk 实现的差异。

您的文档指向 Oracle 站点,但您没有使用 Oracle JDK。您的 OpenJDK 实现可能会以不同的方式处理此异常,因此您不会遇到异常。Requested array size exceeds VM limit

来自我的zulu-11 jdk ArrayList的一些信息.java实现:

/**
 * The maximum size of array to allocate (unless necessary).
 * Some VMs reserve some header words in an array.
 * Attempts to allocate larger arrays may result in
 * OutOfMemoryError: Requested array size exceeds VM limit
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

推荐