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信息、锁信息等等。类型指针指向方法取得类元信息上。

4