15

当我运行以下程序(运行时"java -Xmx151M -cp . com.some.package.xmlfun.Main"):

package com.some.package.xmlfun;
public class Main {

    public static void main(String [] args) {
        char [] chars = new char[50 * 1024 * 1024];

    }
}

我需要将最大内存增加到至少 151M (-Xmx151M)。因此,当我增加数组大小时,需要增加限制:

  • 50 * 1024 * 1024 -> -Xmx151M
  • 100 * 1024 * 1024 -> -Xmx301M
  • 150 * 1024 * 1024 -> -Xmx451M

为什么看起来 java 每个字符需要 3 个字节,而不是文档建议的 2 个字节?

此外,当我类似地创建 long 数组时,它似乎每长需要 12 个字节,而不是 8 个字节,而 int 它需要 6 个字节而不是 4 个。通常看起来它需要 array_size * element_size * 1.5

编译- javac \com\som\package\xmlfun\\*java

与运行- java -Xmx151M -cp . com.some.package.xmlfun.Main

4

4 回答 4

8

我想你所看到的可以很容易地通过 JVM 中的堆的组织方式来解释。

当您将参数传递-Xmx给 JVM 时,您正在定义最大堆大小。但是,它与您可以分配的数组的最大大小没有直接关系。

在 JVM 中,垃圾收集器负责为对象分配内存并清理死对象。垃圾收集器决定了它如何组织堆。

你通常有一个叫做伊甸园空间的东西,然后是两个幸存者空间,最后是终身代。所有这些都在堆内部,GC在它们之间划分最大堆。有关这些内存池的更多详细信息,请查看这个出色的答案:https ://stackoverflow.com/a/1262474/150339

我不知道默认值是什么,它们可能确实取决于您的系统。我刚刚检查(使用sudo jmap PID)内存池如何划分我在运行 Ubuntu 64 位和 Oracle Java 7 的系统上运行的应用程序中的堆。该机器有 1.7GB 内存。

在那个配置中,我只传给-XmxJVM,GC按如下方式划分堆:

  • 伊甸园空间约 27%
  • 每个幸存者空间大约 3%
  • 约 67% 为终身一代。

如果您有类似的分布,则意味着您的 151MB 中最大的连续块位于终身代中,大约为 100MB。由于数组是一个连续的内存块,并且您根本不能让一个对象跨越多个内存池,因此它解释了您所看到的行为。

您可以尝试使用垃圾收集器参数。在此处检查垃圾收集器参数:http ://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

你的结果对我来说似乎很合理。

于 2013-06-27T13:46:37.337 回答
6

在 Java HotSpot VM 中,堆分为“新生代”和“老年代”。数组必须在其中任何一个中。新老代大小比率的默认值为 2。 (实际上表示old/new=2

因此,通过一些简单的数学运算,可以看出一个 151MB 的堆可以有 50.33MB 的新生代和 100.67MB 的老年代。一个 150MB 的堆也正好有 100MB 的老年代。您的数组 + 其他所有内容(例如args)将耗尽 100MB,因此产生OutOfMemoryError.


我试着跑

java -Xms150m -Xmx150m -XX:+PrintGCDetails Main > c.txt

并且从c.txt

(...)
堆
 PSYoungGen 总共44800K,使用了 3072K (地址...)
  伊甸园空间 38400K,已使用 8% (...)
  从空间 6400K, 0% 使用 (...)
  到空间 6400K,使用 0% (...)
 ParOldGen 总计102400K,使用了 217K (...)
  对象空间 102400K,已使用 0% (...)
 PSPermGen 总计 21248K,使用了 2411K (...)
  对象空间 21248K,已使用 11% (...)

这些空间并不完全等于我的计算,但它们很接近。

于 2013-06-27T14:04:57.017 回答
1

如果您查看数据的大小(例如使用 Visual GC),您会发现数组的大小确实是每个字符 2 个字节。

这里的问题是 JVM 试图将整个数组放入堆的老年代,而这一代的大小受到新旧代大小比例的限制。

运行 with-XX:NewRatio=5将纠正问题(默认值为 2)。

于 2013-06-27T14:09:57.540 回答
0

我将尝试以布鲁诺的回答为基础。我现在尝试了这段代码:

public static void main(String[] args) throws IOException {
    char [] chars = new char[50 * 1024 * 1024];
    System.out.println(Runtime.getRuntime().freeMemory());
    System.out.println(Runtime.getRuntime().totalMemory());
    System.out.println(Runtime.getRuntime().maxMemory());
}

输出是:

38156248
143654912
143654912

很明显,为 JVM 的某些其他目的留出了 40 MB 空闲空间。我最好的猜测是新一代空间。

于 2013-06-27T14:10:21.790 回答