Java 中对象的内存布局

Java 中对象的内存布局

在 HotSpot 虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

布局示意图如下所示:
在这里插入图片描述

对象头(Header)

HotSpot 虚拟机中,实例对象的对象头包括两部分信息:Mark Word 和 类型指针(Class Pointer)。如果对象是数组对象,则在对象头中还有一块用于记录数组长度的数据。

Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志位、线程持有的锁、偏向线程ID 等信息。Mark Word 在64位虚拟机中的长度为 8个字节 。

类型指针(Class Pointer) ,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定对象是哪个类的实例。在64位虚拟机中,当开启类型指针压缩(UseCompressedClassPointers)时,类型指针长度为 4个字节,否则长度为 8个字节。

数组长度 用于记录数组的长度,虚拟机通过这个数据确定数组的大小。数组长度是非负的 int 类型数值,所以固定占 4个字节

实例数据(Instance Data)

实例数据部分是对象真正存储的有效信息,也就是在程序代码中所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的都需要记录起来。

这部分的存储顺序会受到虚拟机分配策略参数(FieldsAllocationStyle)和字段在Java源码中定义顺序的影响。

HotSpot 虚拟机默认的分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers:普通对象指针),从分配策略中可以看出,相同宽度的字段总是被分配到一起。在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。如果 CompactFields参数值为true(默认为true),那么子类之中较窄的变量也可能会插入到父类变量的空隙之中。

在64位虚拟机中,oops(Ordinary Object Pointers:普通对象指针)在开启普通对象指针压缩(UseCompressedOops)时,指针长度为 4个字节,否则长度为 8个字节。

对齐填充(Padding)

对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot 虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。因此当对象头和实例数据部分没有对齐的话,就需要通过对齐填充来补全。

通过 JOL 分析Java对象的内存布局

通过 OpenJDK 官方提供的 JOL (Java Object Layout) 工具,我们可以很方便的分析、了解一个Java对象在内存当中的具体布局情况。

依赖如下

<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.10</version>
</dependency>

这里我在64位的 HotSpot 虚拟机环境下进行分析、测试。

通过 java -XX:+PrintCommandLineFlags -version 命令,可以看到 JVM 虚拟机已经开启类型指针压缩和普通对象指针压缩(前边的 + 加号表示开启):

-XX:+UseCompressedClassPointers 
-XX:+UseCompressedOops

编写一个简单的测试类 User

@Data
public class User {

    private String userName;
    private int age;

    public User() {
    }
}

接下来,我们创建一个 User 对象,并输出对象在内存中占用字节数,以及内存布局情况:

public static void main(String[] args) {

	User user = new User();

	// 输出对象在内存中占用字节大小
	System.out.println(ObjectSizeCalculator.getObjectSize(user));
	// 输出对象在内存中的布局
	System.out.println(ClassLayout.parseInstance(user).toPrintable());

}

输出如下:
在这里插入图片描述
分析:

  1. Mark Word 占 8 个字节;
  2. 因为开启了类型指针压缩(+UseCompressedClassPointers),所以类型指针占 4 个字节;
  3. 实例数据中 age 为 int 类型,占 4 个字节;
  4. 实例数据中 userName 为普通对象类型,并且已经开启普通对象指针压缩(+UseCompressedOops),所以 userName 占 4 个字节;
  5. 对象头+实例数据共占 20 字节,20 不是 8 的倍数,所以对齐填充处再填充 4 个字节至 24 个字节,所以最终占 24 个字节。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓呆同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值