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

时间:2018-05-24 06:44:17

标签: java arrays jvm jvm-hotspot

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

我也知道Java数组也是一个对象。

但当我阅读"了解JVM高级功能和最佳实践,第二版"时,它说Java阵列的标题由三部分组成;标记字,类指针和数组长度。

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

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

我希望你们能给我一些示例Java代码,向我展示如何计算对象的内存大小,不限于数组对象。

2 个答案:

答案 0 :(得分:1)

你可以使用JOL framework来测试这一点,由所有强大的Aleksey Shipilev撰写。

使用它实际上非常简单,首先让我们定义你关心的布局:

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

所以你得到12 bytes标题,(4 + 4表示两个标题,加4表示数组大小,它是int);然后你得到40个字节,用于数组将保存的10个整数。

接下来是我不完全确定我明白的事情。到目前为止,我们有52个字节,对象在8 bytes上对齐,这意味着此52值应舍入为56 bytes以将其与8对齐。

相反,它说12 bytes loss due to the next object alignment。我只能猜测可能有两件事,first read the comments heremay be some field is there for internal purpose only

不打算在这里显示其余的示例输出(你也可以这样做),我会问一个关于我很快就填充的事情的后续问题。

答案 1 :(得分:1)

实际的对象大小是特定于实现的,并且甚至不要求对象在其生命周期内保持所需的大小。

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

  

对象标题布局

     

对象标题包含本机大小的标记字,klass字,32位长字(如果对象是数组),32位间隙(如果对齐规则需要),然后为零或更多实例字段,数组元素或元数据字段。 (有趣的琐事:Klass元对象在klass词之后立即包含一个C ++ vtable。)

     

间隙字段(如果存在)通常可用于存储实例字段。

     

如果UseCompressedOops为false(并且始终在ILP32系统上),则mark和klass都是本机机器字。对于阵列,差距始终存在于LP64系统上,并且仅存在于ILP32系统上具有64位元素的阵列上。

     

如果UseCompressedOops为true,则klass为32位。非数组在klass之后立即有一个间隙字段,而数组在klass之后立即存储长度字段。

对象的大小计算“标题+属性+引用”是不正确的。首先,对对象的引用不是指示对象大小的一部分。可以对同一个对象进行任意数量的引用,但这些引用根本不必在堆内存或RAM中,因为优化的代码可以纯粹通过CPU寄存器访问对象。

此外,正如上面的引文中所暗示的,存在对齐规则,这些规则使得字段所需的存储器的计算变得非常重要。如果存在适合类型的字段,则标题中可能存在可用于存储实例字段的间隙。虽然可以安排同一类的字段来最小化填充,但是子类必须与超类的布局一起使用,可能会向其添加更多字段,并且如果它具有拟合类型的字段,则可能仅填充间隙,否则,由于类层次结构可能会有更多的差距。

对于数组,您可以从引用的文章中推导出32位HotSpot表示使用12字节的标头,除非类型为long[]double[],在这种情况下它将是16字节

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