Java char数组似乎每个char需要超过2个字节

时间:2013-06-27 13:40:14

标签: java jvm char sizeof primitive

当我运行以下程序(使用"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个字节?

此外,当我类似地创建长数组时,它似乎每个长度需要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 个答案:

答案 0 :(得分:8)

我猜您所看到的内容可以通过JVM中的堆的组织方式轻松解释。

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

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

您通常会有一些名为 Eden space 的东西,然后是两个幸存者空间,最后是终身代。所有这些都在堆内,并且GC在它们之间划分最大堆。有关这些内存池的更多详细信息,请查看以下精彩答案:https://stackoverflow.com/a/1262474/150339

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

在该配置中,我只将-Xmx传递给JVM,GC按如下方式划分堆:

  • 伊甸园空间约27%
  • 每个幸存者空间约3%
  • 约有67%的终身人员。

如果你有类似的发行版,这意味着151MB的最大连续块是在终身代,并且大约是100MB。由于数组是连续的内存块,并且您根本无法将对象跨越多个内存池,因此它解释了您所看到的行为。

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

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

答案 1 :(得分: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

(...)
Heap
 PSYoungGen      total 44800K, used 3072K (addresses...)
  eden space 38400K, 8% used (...)
  from space 6400K, 0% used (...)
  to   space 6400K, 0% used (...)
 ParOldGen       total 102400K, used 217K (...)
  object space 102400K, 0% used (...)
 PSPermGen       total 21248K, used 2411K (...)
  object space 21248K, 11% used (...)

这些空格并不完全等于我的计算,但它们就在附近。

答案 2 :(得分:1)

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

这里的问题是JVM尝试将整个数组放在旧一代堆中,并且这一代的大小受新旧一代大小的比例限制。

使用-XX:NewRatio=5运行可以解决问题(默认值为2)。

答案 3 :(得分: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

很明显,40 MB可以用于JVM的其他目的。我最好的猜测是新一代的空间。