【JUC】Java对象内存布局和对象头
文章目录
- 【JUC】Java对象内存布局和对象头
- 1. 对象的内存布局
- 1.1 对象头
- 1.1.1 对象标记
- 1.1.2 类元信息/类型指针
- 1.2 实例数据
- 1.3 对齐填充
- 2. 测试
1. 对象的内存布局
在 HotSpot 虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
1.1 对象头
对象头包含两个部分:
- 对象标记(Mark Word)
- 类元信息(又称类型指针)
1.1.1 对象标记
对象标记中包括以下信息:
- 对象的哈希码:在Java中,每个对象都有一个唯一的哈希码,用于识别该对象。
- 对象的锁状态:Java中的对象可以被加锁以进行线程同步。对象标记中的锁状态信息记录了该对象是否被加锁。
- GC相关信息:Java的垃圾回收器需要知道每个对象是否可达,以便决定是否回收该对象。对象标记中存储了与垃圾回收相关的信息,例如对象是否被标记为可达或已经被回收。
在64位系统中,Mark Word
占了8个字节,类型指针占了8个字节,一共是16个字节。
问:创建一个Object对象占用了多少字节?
答:一个普通的object对象没有实例数据,所以只有对象标记和类型指针加起来的的大小,也就是16字节。
Mark Word
被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word
里存储的数据会随着锁标志位的变化而变化。
1.1.2 类元信息/类型指针
**类型指针(Class Pointer)**就是用来指向该对象的类元数据信息的指针。它指向的是该对象所属的类的Class对象。这个指针是一个内存地址,可以用来确定该对象所属的类以及该类的方法和属性等信息。
在32位的JVM中,Class Pointer占用4个字节;在64位的JVM中,Class Pointer占用8个字节。它是对象头中比较重要的一个字段,因为它可以帮助虚拟机确定这个对象是哪个类的示例,从而进行方法调用、对象实例化等操作。
对象头有多大?
答:在64位系统中,mark word
和 class pointer
都占8个字节,所以对象头的大小为16个字节。
1.2 实例数据
存放类的属性(Field)数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
示例:
class Customer{
int id;
boolean flag = true;
}
创建一个 Customer
对象,实例数据(int占4个字节,boolean占1个字节)共5个字节
1.3 对齐填充
虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐。
如 1.2 示例所示:创建一个 Customer
对象,对象头16个字节,实例数据5个字节,所以对象头+实例数据=21个字节。对齐填充之后,对象的大小为24个字节。
2. 测试
使用 JOL(Java Object LayOut)分析对象在JVM的大小和分布
为什么这里类型指针大小是4字节而不是8字节呢?之后会说明。
为什么GC年龄最大为15?
因为GC年龄采用4位bit存储,所以最大为15,例如 MaxTenuringThreshold
参数默认值就是15。如果我们将其改成16会怎样?
设置jvm参数 -XX:MaxTenuringThreshold=16
,运行任意一个程序,结果如下:
为什么运行结果中类型指针的大小是4字节而不是8字节呢?
在控制台中输入 java -XX:+PrintCommandLineFlags -version
指令,回车,控制台显示如下内容:
-XX:+UseCompressedClassPointers
表示开启了类型指针压缩,以节约空间。
默认配置:启动了压缩指针,-XX:+UseCompressedClassPointers,12 + 4(对齐填充) = 一个对象16字节。
手动配置:关闭了压缩指针,-XX:-UseCompressedClassPointers, 8 + 8 = 一个对象16字节。
乍看之下好像压了等于没压,实际上不是:
一个Person类中,有两个boolean类型的成员变量。
默认配置:启动了压缩指针,对象头是12个字节,实例数据2个字节,那么这个对象大小就是 12 + 2 + 2(对齐填充) = 16字节。
手动配置:关闭了压缩指针,对象头是16个字节,实例数据2个字节,那么这个对象大小就是 16 + 2 + 6(对齐填充) = 24字节。