3分钟理清Java对象头里面的那些杂事
对象头是一个对象用于保存自身状态的区域,在HotSpot虚拟机中,对象在堆内存中存储的布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding),如下如所示:
在Java中普通对象和数组对象的对象在布局上存在一定的差异,数组对象相比普通对象多了数组长度部分,如下图所示:
1、对象头
HotSpot虚拟机对象的对象头部分可以分成mark word和类型指针两类信息,如下如所示:
(1)Mark Word:用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志等等一些信息。为了实现在极小的空间内存储尽量多的数据,Mark Word被设计成动态定义的数据结构,它可以根据对象的状态复用自己的存储空间(也就是说在运行期间Mark Word存储的数据会随着标志位的变化而变化)。如下是32位的HotSpot虚拟机中Mark Word的详细信息:
从上图可以知道一个常见的面试题目答案,为什么GC的分代年龄最大是15次了,因为分代年龄占用4位,我们知道4位二进制的可以表达的最大十位数是15。
(2)Class Pointer:指向它的类型元数据的指针,JVM通过这个指针来确定该对象是哪个类的实例。如下图所示:
(3)数组长度:如果对象是一个数组对象,那在对象头中有一块用于记录数组长度的区域,因为虚拟机可以通过普通对象的元数据信息确定对象的大小,但是如果数组的长度是不确定的,将无法通过元数据中的信息推断出数组的大小。
2、实例数据
实例数据是对象真正存储的有效信息,它记录代码里面所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的字段都会存储到这块区域。如下代码:
public class School {
private int id;
private String name;
.....
}
实例数据部分会存储id、name的数据,如下图所示:
实例数据部分可以没有,如果一个类中没有属性也是存在的,如下所示的代码:
public class School {
}
此时Java对象的布局如下图所示:
所以,任何对象必须都有对象头,但是可以没有实例数据,好比火车必须要有火车头可以没有火车的车厢一样。
3、对齐填充
在Java中每8个字节做一个计数的单元,假设new了一个对象,对象占用了11个字节,11 > 8 但是又不是8的整数倍,此时这个对齐填充就会让对象变成8的整数倍,这里对齐填充补充5字节(也就是11 + 5 = (2*8) = 16)。
4、jvm中的对象的布局
public class Student{
private int age;
private String name;
......
}
public class School {
private int id;
private String name;
private Student student;
......
}
public class TestObject{
public static void main(String[] args){
School school = new School(1, "龙虾编程", new Student());
}
}
每当我们创建School对象的时候,在Java的堆中开辟内存存放对象,在栈上存放栈帧的信息,如下所示是Java对象在jvm中的布局信息:
栈帧上的局部变量表中的school指向堆空间的上的实例,对象头中的类型指针(class pointor)指向方法区的School类元信息,School中的属性数据存在到实例数据这块区域上(如果属性是对象,那么会使用指针指向其对象上)。
5、输出Java对象信息
(1)添加pom
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
(2)输出对象信息
public static void main(String[] args) {
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
输出结果:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
这个结果中输出了Object对象的对象头、类型指针和对齐填充的信息,如下是对各个部分的解释图:
上面可以看出对象头是8字节,类型指针是4个字节,为什么类型指针是4字节呢?这是因为Java中开启了指针压缩导致的。由于对象头中mark word8字节 + 类型指针4字节 = 12字节,所以对齐填充位会自动补齐4字节,这样new Object()默认的大小是16字节。
总结:
(1)Java的对象是由对象头(mark word + class pointor + 数组长度【数组对象特有】)、实例数据和对齐填充组成的。
(2)对象必须要对象头,可以没有实例数据部分。
(3)mark word中存储了哈希码、GC信息、锁信息等等。类型指针指向方法取得类元信息上。