探究Java对象的大小
对象的内存布局
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
本文不讨论对象头中里面有什么,在32位系统中,对象头占8字节;在64位系统中(未开启指针压缩),对象头占16字节。
第二部分实例数据就是对象真正存储的有效数据,也就是咱们在类中定义的变量。之前在这篇 为什么short、byte会被提升为int,及基本类型的真实大小 文章中讨论过,short、byte、boolean 等类型在栈中实际也都是占用4字节,但是在堆中不必哦,例如 byte 数组,每个元素就占一字节,在其它对象中,byte也只占一字节。
第三部分对齐填充并不一定是必需存在的,仅仅是占位符的作用。由于虚拟机都要求对象起始地址必须是8字节的整数倍,所以对象的大小也必须是8字节的整数倍。当对象实例数据部分没有对齐时,就需要对齐填充来补全。(64位开启指针压缩后,对象头只有12字节,也可能会对齐填充,哪不齐就填哪,不是一定在末尾)
JOL工具
本文使用 JOL 工具测量对象的大小,JOL全称为Java Object Layout,是分析JVM中对象布局的工具,添加依赖:
1 | implementation("org.openjdk.jol:jol-core:0.14") |
使用方式,可以传 类 或者 实例对象。
1 | System.out.println(ClassLayout.parseInstance(yourObject).toPrintable()); |
影响对象大小的启动参数
这节会讨论一些会影响对象大小的启动参数,所有启动参数的解释都可以在源码中找到: http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/runtime/globals.hpp?utm_source=ld246.com
CompactFields
-XX:+CompactFields :在前一个字段之间的空白处分配非静态字段,默认开启。
开启该参数后,比较窄的变量可能会打破原有顺序,插入到更前的空隙位置中。
1 | // 测试类 |
UseCompressedOops
由于在32位系统中,最大获取的空间地址为 4GB,对于有些应用而言不够,因此需要使用64位系统。
之前说了,在64位系统中,对象头所占空间更大,有16字节,不仅如此,原本占4字节的对象指针在64位系统中(未开启指针压缩)也膨胀了一倍,变为8字节,因此在64位系统下,会着实浪费不少内存空间。
此时,我们就可以开启指针压缩了。
-XX:+UseCompressedOops:开启指针压缩,只对 64 位系统有效,默认开启
以下是测试类A
1 | class A { |
再看看数组对象的压缩情况:
1 | public static void main(String[] args) { |
发现开启压缩后,对象头的大小由20字节(比普通对象大是因为要多存储数组大小)变为16字节,且少了一个4字节的对齐填充。
64位系统下开启指正压缩后,有以下变化:
- 对象头由 16 字节变为 12 字节。
- 对象引用由 8 字节变为 4 字节。
- 数组对象的对象头由20字节变为16字节。(由于还少了一次对齐填充,能压缩8字节对象头)