如何计算Java数组的内存大小?

How to calculate Java array's memory size?

我知道如何通过添加三个部分来计算 Java object 的内存大小:header + 属性 + 引用。

我也知道 Java 数组也是 object。

但是当我读到 "Understanding the JVM advanced Features and Best Practices, second Edition" 时,它说 Java 数组的 header 由三部分组成;标记字,class指针和数组长度。

在 Hotspot 64 位 JVM 中总是 24 字节。

但是在 32 位 JVM 中,如何计算 Java 数组的内存大小?

我希望你们能给我一些示例 Java 代码来告诉我如何计算一个 object 的内存大小不限于数组 object.

您可以使用由全能的 Aleksey Shipilev 编写的 JOL framework 进行测试。

使用其实很简单,先定义好你关心的Layouts:

Layouter layout32Bits =  new HotSpotLayouter(new X86_32_DataModel());
Layouter layout64Bits = new HotSpotLayouter(new X86_64_DataModel());
Layouter layout64BitsComp = new HotSpotLayouter(new X86_64_COOPS_DataModel());

然后,我们来定义一个数组,看看结果:

int [] ints = new int[1];
System.out.println(ClassLayout.parseInstance(ints, layout32Bits).toPrintable());
System.out.println(ClassLayout.parseInstance(ints, layout64Bits).toPrintable());
System.out.println(ClassLayout.parseInstance(ints, layout64BitsComp).toPrintable());

让我们 运行 一次一个。对于 32bits VM:

  [I object internals:
  OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
  0     4        (object header)                           09 00 00 00 (00001001 00000000 00000000 00000000) (9)
  4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
  8     4        (object header)                           10 0b 40 29 (00010000 00001011 01000000 00101001) (692062992)
 12    40    int [I.<elements>                             N/A
 52    12        (loss due to the next object alignment)
 Instance size: 64 bytes
 Space losses: 0 bytes internal + 12 bytes external = 12 bytes total

因此 headers 得到 12 bytes,(两个 headers 为 4 + 4,加上数组大小为 4,它是一个 int) ;然后你得到 40 个字节,用于数组将保存的 10 个整数。

接下来是我不太确定自己理解的内容。到目前为止,我们有 52 个字节并且 objects 在 8 bytes 上对齐,这意味着这个 52 值应该四舍五入为 56 bytes 以使其与 8 对齐。

而是显示 12 bytes loss due to the next object alignment。我只能猜测可能有两件事,first read the comments here or may be some field is there for internal purpose only

不打算在这里显示示例输出的其余部分(您也可以这样做),我很快就会问一个 follow-up 关于我不清楚的关于填充的问题。

实际的 object 大小是特定于实现的,甚至没有要求 object 所需的大小在其生命周期内保持不变。

an article on wiki.openjdk.java.net 说明:

Object header layout

An object header consists of a native-sized mark word, a klass word, a 32-bit length word (if the object is an array), a 32-bit gap (if required by alignment rules), and then zero or more instance fields, array elements, or metadata fields. (Interesting Trivia: Klass metaobjects contain a C++ vtable immediately after the klass word.)

The gap field, if it exists, is often available to store instance fields.

If UseCompressedOops is false (and always on ILP32 systems), the mark and klass are both native machine words. For arrays, the gap is always present on LP64 systems, and only on arrays with 64-bit elements on ILP32 systems.

If UseCompressedOops is true, the klass is 32 bits. Non-arrays have a gap field immediately after the klass, while arrays store the length field immediately after the klass.

您计算“header + 属性 + 参考”object 的大小不正确。首先,对 object 的引用不是引用对象 object 大小的一部分。可以有任意数量的对同一个 object 的引用,但这些引用根本不必在堆内存或 RAM 中,因为优化代码可以纯粹通过 CPU 注册。

此外,正如上面引述中所暗示的,有一些对齐规则使得计算字段所需的内存变得非常重要。 header 中可能存在间隙,如果有适合它的类型的字段,则可能用于存储实例字段。虽然可以安排相同 class 的字段以最小化填充,但 subclass 必须与 superclass' 布局一起使用,可能会向其添加更多字段并且可能只填充如果它有拟合类型的字段,则在间隙中,否则,由于 class 层次结构,可能会有更多间隙。

对于数组,您可以从引用的文章中得出,32 位 HotSpot 表示使用 12 字节的 header,除非类型为 long[]double[],其中如果它将是 16 个字节。

对于 64 位实现,UseCompressedOops 选项(默认情况下启用)允许将 64 位标记字与 32 位 klass 和 32 位长度组合到 header 总共 16 个字节。仅当 UseCompressedOops 关闭时,header 将为 24 个字节。