【问题标题】:Huge arrays throws out of memory despite enough memory available尽管有足够的可用内存,但巨大的数组仍会耗尽内存
【发布时间】:2011-11-10 00:55:28
【问题描述】:

使用-Xmx1G 标志来提供一个1 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,因此很容易放入内存中。

丢失的记忆去哪儿了?

【问题讨论】:

  • 你的虚拟机是 64 位的吗? 32 位 vm 可能无法像内存碎片一样使用那么多连续内存。
  • MB = 兆字节,Mb = 兆位。 ;)

标签: java arrays memory


【解决方案1】:

在 Java 中,堆中通常有多个区域(和子区域)。你有一个年轻而老牌的地区,收藏家最多。大型数组会立即添加到永久区域,但是根据您的最大内存大小,将为年轻空间保留一些空间。如果您缓慢地分配内存,这些区域会调整大小,但是像这样的大块可能会像您所见的那样简单地失败。

鉴于内存通常相对便宜(并非总是如此),我只会将最大值增加到您希望应用程序在使用那么多时失败的程度。

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

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

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

写起来有点乏味,但有一个很大的优势,它几乎不使用堆。 (有不到 1 KB 的开销)

【讨论】:

  • 那么JVM垃圾是如何收集这些内存的呢?
  • 通常你不想丢弃这么大的结构。但是,当 IntBuffer 被收集时,它包含一个 Cleaner+Deallocator 对象,该对象在清理时执行 freeMemory。您可以通过调用((DirectBuffer) buffer).cleaner().clean(); 强制它在 Sun/Oracle jvm 中释放内存而无需 GC,但这使用内部 API。
  • @JustJeff,直接内存使用本机 C 内存。它根据需要从操作系统获取更多内存。最大直接内存与 Java 6 中默认的最大堆大小相同。但它超出了堆,对 GC 时间没有影响。
  • @JustJeff,为了您的兴趣,您可以使用必须显式释放的 Unsafe 类分配大面积的 C 内存。它确实允许您创建操作系统支持的任何大小的块内存。这绝对是您自己的风险特征。
  • 非常有趣和有见地的讨论。 :)
【解决方案2】:

有足够的可用内存但不是一个连续的 内存块,根据数组的需要。

您能否使用使用较小块的不同数据结构 内存,还是几个更小的数组?

例如,以下代码确实适用于-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);
        }
    }
}

【讨论】:

    【解决方案3】:

    堆内存分为三个空间:

    • 老一代
    • 幸存者空间
    • 伊甸空间

    一开始,这个对象将存在于老年代,并会在这里停留一段时间。

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

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

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

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

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

    Ergonomics in the 5.0 Java[tm] Virtual Machine

    Tuning Garbage Collection with the 5.0 Java[tm] Virtual Machine

    【讨论】:

      猜你喜欢
      • 2015-02-12
      • 1970-01-01
      • 2013-11-05
      • 2019-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多