Java在真正内存不足之前抛出内存异常?

时间:2012-08-03 17:13:26

标签: java arrays memory out-of-memory large-data

我希望创建一个大型的int数组,几乎可以填充JVM可用的所有内存。拿这个代码,例如:

    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数组的开销,因为它有所有那些对其他数组的引用来存储。)

有什么想法吗?

3 个答案:

答案 0 :(得分:5)

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

此外,一些内存已经被JRE用完了。 200k是 nothing ,具有今天的内存大小,对于大多数应用程序来说,10M堆几乎是不切实际的小。

阵列的实际开销相对较小,在32位VM上它的12字节IIRC(如果大小小于最小粒度,则为浪费,这是AFAIK 8字节)。因此,在最坏的情况下,每个阵列的开销大约为19字节。

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

答案 1 :(得分:1)

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

答案 2 :(得分:0)

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

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

xxxxxxxxxx 

然后,您在某处分配对象0。这使你看起来像堆:

xxxxxxx0xx

现在,您无法再分配这10个x个空格。尽管可用内存为9 x s,但您甚至无法分配8 x

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

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

还有许多其他因素需要考虑,其中一些因素包括:VM中的内存泄漏(不太可能)或您的应用程序(也不太可能是一个简单的场景),使用{{1的不可靠性(GC可能在调用后立即运行,可用的可用内存可能会更改),每个特定JVM的实现细节等。

关键是,根据经验,并不总是希望您的应用程序可以使用全部Runtime.freeMemory()