6

我希望创建一个几乎填满 JVM 可用内存的大型 int 数组。以这段代码为例:

    final int numBuffers = (int) ((runtime.freeMemory() - 200000L) / (BUFFER_SIZE));
    System.out.println(runtime.freeMemory());
    System.out.println(numBuffers*(BUFFER_SIZE/4)*4);
    buffers = new int[numBuffers*(BUFFER_SIZE / 4)];

当以 10M 的堆大小运行时,这会引发 OutOfMemoryException,尽管 printlns 的输出是:

9487176
9273344

我意识到阵列会有一些开销,但肯定不是 200k 吗?为什么java无法为它声称有足够空间的东西分配内存?在 Java 运行它之前,我必须设置减去 4M 左右的常量(此时 printlns 看起来更像:9487176 5472256)

更令人困惑的是,如果我用 2D 数组替换缓冲区:

buffers = new int[numBuffers][BUFFER_SIZE / 4];

然后它使用上面显示的 200k 减法毫无怨言地运行 - 即使两个数组中存储的整数数量相同(并且 2D 数组的开销不会大于 1D 数组的开销,因为它得到了所有那些对要存储的其他数组的引用)。

有任何想法吗?

4

3 回答 3

5

VM 会将堆内存划分为不同的区域(主要用于垃圾收集器),因此当您尝试分配几乎整个堆大小的单个对象时,您将耗尽内存。

此外,JRE 已经使用了一些内存。200k对于今天的内存大小来说不算什么,而 10M 堆对于大多数应用程序来说几乎是不切实际的小。

数组的实际开销相对较小,在 32 位 VM 上,它的 12 字节 IIRC(加上如果大小小于最小粒度,即 AFAIK 8 字节,则会被浪费掉)。所以在最坏的情况下,每个数组会有 19 个字节的开销。

请注意,Java 没有 2D(多维)数组,它在内部将其实现为数组数组。

于 2012-08-03T17:23:23.523 回答
1

在 2D 情况下,您分配了更多、更小的对象。内存管理器反对占用大部分堆的单个大对象。为什么这令人反感是垃圾收集方案的一个细节——这可能是因为它可以在几代之间移动较小的对象,而堆不能容纳移动单个大对象。

于 2012-08-03T17:23:13.677 回答
0

这可能是由于内存碎片和 JVM 在给定当前堆的情况下无法分配该大小的数组。

想象一下你的堆有 10x长:

xxxxxxxxxx 

然后,你在这里分配一个对象0。这使您的堆看起来像:

xxxxxxx0xx

现在,您不能再分配这 10 个x空间。x尽管可用内存是 9 秒,但您甚至不能分配 8x秒。

事实是数组数组不会遇到同样的问题,因为它不是连续的。

编辑:请注意,以上是对问题的一个非常简单的看法。当堆中需要空间时,Java 的垃圾收集器将尝试收集尽可能多的内存,并且如果真的非常需要,尝试压缩堆。但是,某些对象可能不可移动或不可回收,从而产生堆碎片并使您处于上述情况。

您还必须考虑许多其他因素,其中一些包括:VM 中的内存泄漏(不太可能)或您的应用程序(对于简单的场景也不太可能),使用Runtime.freeMemory()的不可靠性(GC 可能运行正确调用之后可用的可用内存可能会改变),每个特定 JVM 的实现细节等。

关键是,根据经验,不要总是期望Runtime.freeMemory()您的应用程序可以使用全部数量。

于 2012-08-03T17:26:24.443 回答