Java char 数组似乎每个 char 需要 2 个以上的字节

2022-09-03 14:48:52

当我运行以下程序(运行) 时:"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


答案 1

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

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

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

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

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

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

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

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

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

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


答案 2

在Java HotSpot VM中,堆分为“新一代”和“旧一代”。数组必须位于其中任何一个中。新/旧代大小之比的默认值为 2。(这实际上表示旧/新= 2

因此,通过一些简单的数学运算,可以显示一个151MB的堆可以有50.33MB的新一代和100.67MB的旧一代。此外,一个150MB的堆正好有100MB的旧一代。您的数组 + 其他所有内容(例如 )将耗尽 100MB,从而产生 .argsOutOfMemoryError


我试图用

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 (...)

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