Java: 在堆上可靠地分配大数组

Java: reliably allocate large array on heap

任务

分配 X=4..8MB 的字节数组(在堆上),例如使用 ByteBuffer.allocate() 这样它就不会导致 OutOfMemoryError。不允许拆分数组并将其分成更小的部分进行处理。请注意,分配发生在堆上,这不是直接的 ByteBuffer。

挑战

问题

Java有没有办法像下面这样编码?

if (<I can reliably allocate an array sized X bytes on heap right now>) {
     ByteBuffer.allocate(X);
}

不,在 Java 中没有可靠的方法可以做到这一点。

有多种方法可以获得可用内存的估计值或尽力而为的猜测,但没有可靠的方法。另请注意,即使存在这样的事情,另一个线程也可以更改条件和分配调用之间的可用数量。

这个 related answer 包含获得这样一个估计的方法,并且还解释了为什么这个不可靠的一些原因。

做的想法存在根本问题

if (<I can reliably allocate an array sized X bytes on heap right now>) {
    ByteBuffer.allocate(X);
}

称为“先检查后执行”反模式。无论 if 条件中的检查应该如何工作,您都需要确保它在检查和后续操作(即分配)之间不会发生变化。

为确保结果不变,您不仅需要停止同一 JVM 的所有其他线程执行分配(或完成并发垃圾收集),还需要阻止同一 JVM 的所有其他进程机器分配内存,因为操作系统可能没有专门为您的 JVM 保留内存,但仍允许其他处理在此时使用它。

条件本身具有您的问题中已经指出的挑战,正如您自己所说,当 JVM 能够即时重新配置它们时,所有这些对实现特定内存区域的摆弄都没有实际意义。由于这通常是对垃圾回收结果的响应,因此您需要先执行完整的垃圾回收,以确定结果情况。只有在这种情况下,如果我们能够阻止所有其他线程和进程进行分配,我们才能确保另一个 GC 不会改变这种情况。

在某些 JVM 上,可靠地触发垃圾收集的唯一方法是执行实际分配。

因此,您需要一种方法来自动执行检查,然后进行实际分配,以确保无论环境中发生什么情况或内存不可用的答案,您都可以使用内存。这种机制确实存在。只需调用 ByteBuffer.allocate(X) ,如果它正常完成,返回的引用可确保只要您保留内存就一直可用。否则,抛出的 OutOfMemoryError 表示内存不可用。既然存在这种机制,就没有理由提供第二个具有相同结果的机制。