Java在它之前抛出内存不足异常';什么东西真的记不起来了?

Java在它之前抛出内存不足异常';什么东西真的记不起来了?,java,arrays,memory,out-of-memory,large-data,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[num

我希望创建一个大的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,尽管PrintLN的输出为:

9487176
9273344
我意识到阵列将有一些开销,但肯定不是20万?为什么java不能为它声称有足够空间的东西分配内存?在Java运行之前,我必须将该常数设置为大约4M(此时println看起来更像: 9487176 5472256 )

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

buffers = new int[numBuffers][BUFFER_SIZE / 4];
然后,使用上面显示的200k减法,它可以毫无怨言地运行—即使两个数组中存储的整数数量相同(而且2D数组的开销不会比1D数组的开销大,因为它要存储对其他数组的所有引用)


有什么想法吗?

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

虚拟机将堆内存划分为不同的区域(主要用于垃圾收集器),因此,当您试图分配一个几乎等于整个堆大小的对象时,内存将耗尽

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

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


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

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

想象你的堆有10
x
长:

xxxxxxxxxx 
然后,在此处分配一个对象
0
some。这使堆看起来像:

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

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

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

<> P.>还有很多其他因素需要考虑,其中一些因素包括:内存泄漏(VM)(不是很可能)或应用程序(也不适用于简单的场景),使用<代码>运行时的不可靠性。FReMeMeRyRyE()/Calp>(GC可能在调用之后运行,可用空闲内存可能改变)每个特定JVM的实现细节等


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

缓冲区大小是否设置为堆的大小?否,在本例中,它被设置为16384。因此,这就是填充数组后您希望使用的内存量?可能java无法为数组分配所需大小的连续内存块,因此失败-但在2D数组中,它可以将较小的数组放入多个不同的,较小的连续部分,因此是罚款?我不知道内存碎片在Java中是一个问题。啊,不,我的解释不完全清楚-numBuffers值将空闲内存划分为大小为BUFFER\u的段,以便在2D数组场景中使用。我不明白为什么不能分配相同维度的1D数组。这是有道理的-有没有办法解决这个问题(至少在某种程度上)让我得到我的巨型数组?显然是更大的堆:)您可以在命令行上使用垃圾收集器设置。这允许对“代”的大小进行一些控制。它可能会被调整为允许分配到非常接近最大堆大小的位置,但可能会影响GC的性能。因为我的应用程序除了使用这一个数组之外几乎什么都不做,所以GC性能不是一个大问题。至少从Java5开始,Java就能够重新定位堆上的对象。请不要散布FUD。@Durandal——Java可以重新定位某些对象。大型对象是一种特殊情况。@Durandal-除了堆压缩之外,还有对象固定,这会阻止GC自由移动或收集某些对象。这会导致内存碎片。答案非常简单,只是为了传达信息。@Isoliviera:承认,在某些情况下,对象可能无法移动。但您的回答完全忽略了对象通常是可由GC移动的,而这又使它看起来像是永远不可移动的对象。@HotLicks:我找不到“大型对象从不移动”的任何来源。你有链接吗?