尽管有足够的内存,但巨大的数组仍会抛出内存不足的错误。

15

使用 -Xmx1G 标志提供一个一GB的堆,以下代码将按预期工作:

public class Biggy {
    public static void main(String[] args) {
        int[] array = new int[150 * 1000 * 1000];
    }
}

该数组应该表示大约600 MB的数据。

然而,以下代码会抛出OutOfMemoryError错误:

public class Biggy {
    public static void main(String[] args) {
        int[] array = new int[200 * 1000 * 1000];
    }
}

尽管该数组应该表示大约800 MB,因此可以轻松地放入内存中。

那么缺失的内存去哪了?


1
你的虚拟机是64位的吗?32位的虚拟机可能无法使用那么多连续内存,因为内存会出现碎片。 - J-16 SDiZ
MB代表兆字节,Mb代表兆比特。 ;) - Peter Lawrey
3个回答

20

在Java中,通常有多个区域(和子区域)在堆中。您有一个年轻的和一个存活的区域,大多数收集器都会使用。大型数组会直接添加到存活的区域中,但是根据您的最大内存大小,一些空间将被保留用于年轻的区域。如果您分配内存速度较慢,这些区域将调整大小,但是像这样的大块可能会失败,就像您所看到的那样。

考虑到内存通常相对便宜(并非总是如此),我会将最大值增加到应用程序使用该内存时会导致失败的点。

顺便说一句:如果您有这样一个大结构,您可能考虑使用直接内存。

IntBuffer array = ByteBuffer.allocateDirect(200*1000*1000*4)
                            .order(ByteOrder.nativeOrder()).asIntBuffer();

int a = array.get(n);
array.put(n, a+1);

虽然写起来有点麻烦,但它有一个很大的优点,几乎不使用堆内存。(开销小于1KB)


1
那么JVM如何对这些内存进行垃圾回收呢? - Clark Bao
1
通常情况下,您不希望丢弃这样一个大的结构。但是,当IntBuffer被收集时,它包含一个Cleaner+Deallocator对象,当其被清理时会执行freeMemory操作。您可以通过调用((DirectBuffer) buffer).cleaner().clean();来强制释放内存,而不需要在Sun/Oracle jvm中进行GC,但这使用了一个内部API。 - Peter Lawrey
1
@JustJeff,直接内存使用本地C内存。它根据需要从操作系统获取更多内存。默认情况下,Java 6中的最大直接内存与最大堆大小相同。但它超出了堆并且不会影响GC时间。 - Peter Lawrey
1
@JustJeff,如果你感兴趣的话,你可以使用Unsafe类来分配大块C内存,但你必须显式地释放它。这确实允许你创建任何大小的块内存,只要操作系统支持。这绝对是一个自担风险的功能。 - Peter Lawrey
1
非常有趣和深入的讨论。 :) - Clark Bao
显示剩余7条评论

3

虽然有足够的内存可用,但并非作为单个连续的内存块,这是数组所需的。

您可以使用不同的数据结构来使用较小的内存块或多个较小的数组吗?

例如,以下代码可以使用 -Xmx1G

public class Biggy {
    public static void main(String[] args) {
        int [][]array = new int[200][];
        for (int i = 0; i < 200; i++) {
                array[i] = new int[1000 * 1000];
                System.out.println("i=" + i);
        }
    }
}

1

堆内存被分为三个空间:

  • 老年代
  • 幸存者区
  • 伊甸园区

在开始时,该对象将存在于老年代,并将在此停留一段时间。

默认情况下,虚拟机在每次收集时会增加或缩小堆,以尝试保持每次收集中自由空间与活动对象的比例在特定范围内。这个目标范围由参数-XX:MinHeapFreeRatio=和-XX:MaxHeapFreeRatio=设置为百分比,并且总大小在-Xms以下,在-Xmx以上受到限制。

我的jvm中默认比率为30/70,因此老年代中对象的最大大小受到限制(使用-Xmx1G),限制为700Mb(顺便说一句,当使用默认jvm参数运行时,我遇到了相同的异常)。

但是,您可以使用jvm选项调整代的大小。例如,您可以使用参数-Xmx1G -XX:NewRatio=10运行您的类,new int[200 * 1000 * 1000];将成功。

据我所知,Java并不是为了在内存中存储大型对象而设计的。应用程序中内存的典型使用情况是一堆相对较小的对象的图形,通常只有在所有空间都用完时才会出现OutOfMemoryError。

以下是几篇有用(且有趣可读)的文章:

5.0 Java[tm]虚拟机中的人体工程学

使用5.0 Java[tm]虚拟机调整垃圾收集


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接