首页 > 解决方案 > 分配大于堆的数组时出现意外的 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 更新

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

标签: javaout-of-memoryheap-memoryjava-11

解决方案


引用的文档,了解 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 的标准化接口。(实际上,这样的概念可能定义不明确,因为不同数组类型的最大数组大小可能不同,或者它可能会从一个时刻到下一个时刻发生变化。)


推荐阅读