HotSpot 中的 Java 对象布局


OOP
OOP与klass

JOL 工具简介
在具体开始研究对象的内存结构之前,先介绍一下我们要用到的工具,openjdk 官网提供了查看对象内存布局的工具 jol(java object layout),可以在 maven 中引入坐标使用。
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.14</version></dependency>
在代码中使用 jol 提供的方法查看 jvm 信息:
package com.justdodt.jvm;import org.openjdk.jol.vm.VM;/*** @Author:JustDoDT* @Description:* @Date:Create in 21:49 2021/11/21* @Modified By:*/public class JvmInfo {public static void main(String[] args) {System.out.println(VM.current().details());}}
运行结果为:

通过打印出来的信息,可以看到我们使用的是 64位 jvm,并开启了指针压缩,对象默认使用 8 字节对齐的方式。通过 jol 查看对象内存布局的方法,将在后面例子具体展开。
对象头(Header)
对象头示例
package com.justdodt.jvm;import org.openjdk.jol.info.ClassLayout;/*** @Author:JustDoDT* @Description:* @Date:Create in 22:11 2021/11/21* @Modified By:*/public class JvmClassLayout {public static void main(String[] args) throws Exception{JvmClassLayout jvmClassLayout = new JvmClassLayout();//查看对象的内存布局System.out.println(ClassLayout.parseInstance(jvmClassLayout).toPrintable());}}
执行代码,查看输出结果:

OFFSET:偏移地址,单位为字节 SIZE:占用内存大小,单位为字节 TYPE:Class 中定义的类型 DESCRIPTION:类型描述,Object header 表示对象头,alignment 表示对齐填充 VALUE:对应内存中存储的值
8B (mark word) + 4B (klass pointer) + 0B (instance data) + 4B (padding)
Mark Word

类型指针(Class Pointer)
未开启指针压缩时,类型指针占用 8B(64bit) 开启指针压缩情况下,类型指针占用4B(32bit)
#开启指针压缩:-XX:+UseCompressedOops#关闭指针压缩:-XX:-UseCompressedOops
指针压缩原理
0~2^64-1
0 ~ (2^32-1)*8
为什么要引入压缩指针
数组长度(Length)[option]
实例数据(Instance Data)
-XX:FieldsAllocationStyle=0,表示先分配对象,然后再按照 double/long/ints、shorts/chars、bytes/booleans的顺序分配其他字段,也就是类中声明的相同宽度的字段总是会被分配在一起,而相同宽度字段的顺序则是他们在 class 文件中声明的顺序。 -XX:FieldsAllocationStyle=1(默认值),表示先按照double/long、ints、chars/shorts、bytes/booleans的顺序分配属性,然后再分配对象,分配过程中的其他原则上面为0时是保持一致的。
如果是特定的类,例如基本类型的包装类、String、Class、ClassLoader、软引用等类,会先分配对象,然后再按照 double/long、ints、chars/shorts、bytes/booleans的顺序分配,同时-XX:+CompactFields和-XX:FieldsAllocationStyle=1都不会生效。 如果配置-XX:+CompactFields,会将ints、shorts/chars、bytes/booleans、oops的顺序将字段填充到对象头信息与字段起始偏移位置的间隙中去 如果当前类或者类中使用了注解@sun.misc.Contended, 也会打乱上述布局
对齐填充(Padding)
对象头占用内存大小
对象头占用的内存

在 64 位的系统中占用 8 个字节:

普通对象占用内存情况:

数组对象占用内存情况:

实例数据占用内存情况:

案例分析1
涉及 JVM 参数
-XX:+UseCompressedOops(JDK8下默认启用)
package com.justdodt.jvm;import org.openjdk.jol.info.ClassLayout;/*** @Author:JustDoDT* @Description:* @Date:Create in 22:11 2021/11/21* @Modified By:*/public class JvmClassLayout {int i;byte b;String str;public static void main(String[] args) throws Exception{JvmClassLayout jvmClassLayout = new JvmClassLayout();//查看对象的内存布局System.out.println(ClassLayout.parseInstance(jvmClassLayout).toPrintable());}}
输出结果如下:

当不开启 UseCompressedOops 时候,输出结果为:

案例分析2
package com.justdodt.jvm;import org.openjdk.jol.info.ClassLayout;public class JvmClassLayout {int i;byte b;public static void main(String[] args) throws Exception{JvmClassLayout jvmClassLayout = new JvmClassLayout();//查看对象的内存布局System.out.println(ClassLayout.parseInstance(jvmClassLayout).toPrintable());}}
当设置 -Xmx32g -XX:+UseCompressedOops ,输出结果为:

由此可以看出,堆内存超过了压缩指针的最大值。且指针压缩失效,指针长度恢复到8字节。
当设置 -Xmx32g -XX:-UseCompressedOops ,输出结果为:

当设置 -Xmx32g -XX:+UseCompressedOops -XX:ObjectAlignmentInBytes=16时候,输出结果为:

通过指针压缩,利用对齐填充特性,通过映射方式达到了内存地址扩展的效果 指针压缩能够节省内存空间,同时提高了程序的寻址效率 堆内存设置最好不要超过32GB,这时指针压缩将会失效,造成空间的浪费 指针压缩不仅可以作用于对象头的类型指针,还可以作用于引用类型的字段指针,以及引用类型数组指针
参考
https://wiki.openjdk.java.net/display/HotSpot/CompressedOops
https://blog.csdn.net/qq_34212276/article/details/117914322
文章转载自Just do DT,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




