分配大于堆的数组时出现意外的 OutOfMemoryError

Unexpected OutOfMemoryError when allocating an array larger than the heap

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

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

根据 JDK 11 documentation "3 Troubleshoot Memory Leaks > Understand the OutOfMemoryError Exception" :

Exception in thread thread_name: java.lang.OutOfMemoryError: Requested array size exceeds VM limit

Cause: The detail message "Requested array size exceeds VM limit" indicates that the application (or APIs used by that application) attempted to allocate an array that is largerthan the heap size. For example, if an application attempts to allocate an array of 512 MB, but the maximum heap size is 256 MB, then OutOfMemoryError will be thrown with the reason “Requested array size exceeds VM limit."


代码:

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

The documentation has been fixed. 在 v16-ea(抢先体验版)中。

Exception in thread thread_name: java.lang.OutOfMemoryError: Requested array size exceeds VM limit

Cause: The detail message "Requested array size exceeds VM limit" indicates that the application (or APIs used by that application) attempted to allocate an array with a size larger than the JVM implementation limit, irrespective of how much heap size is available.


编辑 4:2021-03-20 更新

JDK 16 is now released with the fixed docs

这可能是 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;

引用的文档,Understand the OutOfMemoryException

Exception in thread thread_name: java.lang.OutOfMemoryError: Requested array size exceeds VM limit

Cause: The detail message "Requested array size exceeds VM limit" indicates that the application (or APIs used by that application) attempted to allocate an array that is larger than the heap size. For example, if an application attempts to allocate an array of 512 MB, but the maximum heap size is 256 MB, then OutOfMemoryError will be thrown with the reason “Requested array size exceeds VM limit."

Action: Usually the problem is either a configuration issue (heap size too small) or a bug that results in an application attempting to create a huge array (for example, when the number of elements in the array is computed using an algorithm that computes an incorrect size).

...不正确。

“请求的数组大小超出 VM 限制”消息 真正 的意思是 JVM 的实现施加了限制。超过此限制将 总是 导致失败,无论有多少堆 space 可用。如果尝试分配接近 Integer.MAX_VALUE 个元素的数组,则会出现带有此消息的 OutOfMemoryError。 (这不是固定限制。见下文。)

相比之下,带有“Java heap space”消息的 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 ( 运行ning 所在的热点或其他)。该库在这里需要有点保守,因为它需要在不同的环境下在不同的 JVM 上 运行,并且没有 JVM 的标准化接口returns“允许的最大数组大小”。 (事实上​​ ,这样的概念可能是 ill-defined,因为最大数组大小可能因不同的数组类型而不同,或者它可能会从一个时刻到下一个时刻发生变化。)