Java 尽管有足够的可用内存,但大型阵列仍会抛出内存

Java 尽管有足够的可用内存,但大型阵列仍会抛出内存,java,arrays,memory,Java,Arrays,Memory,使用-Xmx1G标志来提供1GB的堆,以下功能正常工作: 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[

使用
-Xmx1G
标志来提供1GB的堆,以下功能正常工作:

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,因此很容易放入内存


丢失的内存到哪里去了?

在Java中,堆中通常有多个区域(和子区域)。你有一个年轻的和终身的地区与大多数收藏家。大型阵列会直接添加到永久区域,但是根据您的最大内存大小,会为年轻空间保留一些空间。如果您缓慢地分配内存,这些区域将重新调整大小,但是像您所看到的那样,这样的大块可能会失败

考虑到内存通常相对便宜(并非总是如此),我只会将最大值增加到某个点,如果应用程序使用了那么多内存,您可能会希望它失败

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

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

写起来有点乏味,但有一个很大的优势,它几乎不使用堆。(头上的内存不足1KB)

有足够的可用内存,但不能作为单个连续内存使用 数组所需的内存块

您可以使用使用较小块的不同数据结构吗 还是几个更小的阵列

例如,以下代码确实适用于
-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);
        }
    }
}
公共类Biggy{
公共静态void main(字符串[]args){
int[][]数组=新int[200][];
对于(int i=0;i<200;i++){
数组[i]=新整数[1000*1000];
System.out.println(“i=“+i”);
}
}
}

堆内存分为三个空间:

  • 老一辈
  • 幸存者空间
  • 伊甸园空间
开始时,这个物体将生活在老一代,并将在这里停留一段时间

默认情况下,虚拟机会在每个集合中增大或缩小堆,以尝试将每个集合中活动对象的可用空间比例保持在特定范围内。此目标范围由参数-XX:MinHeapFreeRatio=和-XX:MaxHeapFreeRatio=设置为百分比,总大小以-Xms为界,以-Xmx为界

我的jvm中的默认比率是30/70,所以旧一代中对象的最大大小(使用-Xmx1G)被限制为700Mb(顺便说一句,当使用默认jvm参数运行时,我会遇到相同的异常)

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

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

以下是几篇有用(阅读起来也很有趣)的文章:


您的虚拟机是64位的吗?32位虚拟机可能无法像内存片段那样使用那么多的连续内存。MB=兆字节,MB=兆位。;)那么JVM是如何垃圾收集这些内存的呢?通常情况下,您不希望丢弃如此大的结构。但是,当收集IntBuffer时,它包含一个Cleaner+Deallocator对象,该对象在清理时会释放内存。通过调用
((DirectBuffer)buffer.cleaner().clean(),可以强制它在Sun/Oracle jvm中释放内存,而不使用GC但这使用内部API。@JustJeff,直接内存使用本机C内存。它可以根据需要从操作系统获得更多内存。在Java6中,默认情况下,最大直接内存与最大堆大小相同。但是它在堆之上和堆之外,对GC时间没有影响。@JustJeff,出于您的兴趣,您可以使用不安全的类来分配大面积的C内存,您必须显式释放该类。它允许您创建操作系统支持的任何大小的块内存。这绝对是你自己的风险特性。非常有趣和有见地的讨论。:)