Java Object header对象头分析

使用JOL研究Java对象的布局以及对象头信息:

-- 对象里面的属性是逐个添加

public class A {

    private String name ;

    private int age;

    private boolean flag;

    private List list;

}



public class JOLExample3 {

    public static void main(String[] args) {

        A a = new A();

        out.println(VM.current().details());

        out.println(ClassLayout.parseInstance(a).toPrintable());

    }

}



运行结果:

-- 对象A没有属性

sync.demo.A 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)                           18 0a 1f 18 (00011000 00001010 00011111 00011000) (404687384)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-- 对象A有一个String类型的name属性

sync.demo.A 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)                           18 0a c7 17 (00011000 00001010 11000111 00010111) (398920216)

     12     4   java.lang.String A.name                                    null

Instance size: 16 bytes

Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

-- 对象A有一个String类型的name属性,和一个int类型的age属性

sync.demo.A 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)                           18 0a d7 17 (00011000 00001010 11010111 00010111) (399968792)

     12     4                int A.age                                     0

     16     4   java.lang.String A.name                                    null

     20     4                    (loss due to the next object alignment)

Instance size: 24 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-- 对象A有一个String类型的name属性,和一个int类型的age属性,一个boolean类型的flag属性

sync.demo.A 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)                           18 0a d6 17 (00011000 00001010 11010110 00010111) (399903256)

     12     4                int A.age                                     0

     16     1            boolean A.flag                                    false

     17     3                    (alignment/padding gap)                  

     20     4   java.lang.String A.name                                    null

Instance size: 24 bytes

Space losses: 3 bytes internal + 0 bytes external = 3 bytes total

-- 对象A有一个String类型的name属性,和一个int类型的age属性,一个boolean类型的flag属性,一个List属性

sync.demo.A 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)                           18 0a bf 17 (00011000 00001010 10111111 00010111) (398395928)

     12     4                int A.age                                     0

     16     1            boolean A.flag                                    false

     17     3                    (alignment/padding gap)                  

     20     4   java.lang.String A.name                                    null

     24     4     java.util.List A.list                                    null

     28     4                    (loss due to the next object alignment)

Instance size: 32 bytes

Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

总结:

从上面的对象布局中,随着对象中的属性越来越多,占用的字节就越来越大,但是始终是8的倍数,不够就进行填充;

但是objectheader 始终占用12byte;

总结:

Instance size: 56 bytes 对象的大小;都是8的倍数,64位虚拟机对象的大小必须是8的倍数;

object header:

从上面的结果可以分析出每个对象头的大小占12byte;这12byte分别包含两个word,mark_word 和 klass_word;

mark_word:占8byte,mark_word是我们重点研究的;下面详细说明;

klass_word:剩下的4byte是 klass_word,用于只想对象的元数据;比如class模板;

(loss due to the next object alignment):用于对其填充,当对象大小不是8的倍数的时候,就使用对象填充使其对齐;

官方文档:

Every object (except array) in memory has 2 machine word header. The first one is called mark word and the second one is klass word. Btw arrays have extra 32 bit word filled with array’s length.

Mark word stores identity hashcode, bits used for garbage collection, bits used for locking. To find out more check out the source from OpenJDK.

Klass word stores ordinary object pointer (oop) to class metadata, which describes the object’s layout, methods, and other type information. To find out more check out the metadata source from OpenJDK.

下面来看下mark_word中详细描述了什么:


Java Object header对象头分析_第1张图片

// 64 bits:

// --------

// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)

// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)

// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)

// size:64 ----------------------------------------------------->| (CMS free block)

以上是JVM源码注释

解释一下其中的名词:

unused:没有被使用

identity_hashcode:用于存储对象的hashCode:

identity_hashcode -identity hashcode of the object which is assigned lazily. If System.identityHashCode(obj) is called, it is calculated and written into the object header. When object is locked the identity hashcode value is moved into the monitor object

age:GC分代年龄;

age - number of garbage collections the object has survived. It is incremented every time an object is copied within the young generation. When the age field reaches the value of max-tenuring-threshold, the object is promoted to the old generation

biased_lock:偏向锁状态;

biased_lock - contains 1 if the biased locking is enabled for the class. 0 if the biased locking is disabled for the class. To find out more on biased locking check out my previous

lock:对象状态;分为无锁,轻量级锁,重量级锁,GC标记;

lock -the lock state of the object. 00 - Lightweight Locked, 01 - Unlocked or Biased, 10 - Heavyweight Locked, 11 - Marked for Garbage Collection. To find out more on locking/synchronization check out synchronization post

无锁状态的mark_word:

unused:25:无锁状态下mark wod中前25位没有被使用;

identity_hashcode:31:用于存储对象hashCode,包括前面的25个都是用来存储对象的hashCode,所以mark word前56为是用于存储无锁对象的hashCode;下面会验证一下java对象的hashCode是不是存储在这里面;

unused:1:无锁状态下没有使用;

age:4:GC分代年龄;对象幸存的垃圾收集数量。 每次在年轻一代中复制对象时,它都会递增。 当年龄字段达到max-tenuring-threshold的值时,该对象将被提升为旧一代;

biased_lock:1:偏向锁标识;

lock:2:对象状态;

来看看无锁状态下的mark_word:

先验证一下前56为是不是用于存储对象的hashCode:

public class JOLExample {

    public static void main(String[] args) {

        A a = new A();

        out.println("计算hashcode之前。。。");

        out.println(ClassLayout.parseInstance(a).toPrintable());

        out.println("计算对象a的hashcode,10进制:"+a.hashCode());

        out.println("计算hashcode之后。。。");

        out.println(ClassLayout.parseInstance(a).toPrintable());

    }

}

打印如下:

计算hashcode之前。。。

sync.demo.A 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)                           18 0a cd 17 (00011000 00001010 11001101 00010111) (399313432)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

计算对象a的hashcode,10进制:1213415012

计算hashcode之后。。。

sync.demo.A object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

0     4        (object header)                           01 64 3e 53 (00000001 01100100 00111110 01010011) (1396597761)

      4     4        (object header)                           48 00 00 00 (01001000 00000000 00000000 00000000) (72)

      8     4        (object header)                           18 0a cd 17 (00011000 00001010 11001101 00010111) (399313432)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

通过进制转换工具将对象的10进制hashcode转换成二进制:

   十六进制                                     二进制

1213415012:        01001000 01010011 00111110 01100100

对象头中标红的hashcode:

01100100 00111110 01010011 01001000

两个进行对比一下:

我们打印出来并进行二进制转换的hashCode:  01001000 01010011 00111110 01100100

JOL打印出来的object header中的hashCoxe:    01100100 00111110 01010011 01001000

是不是发现不一样?那是因为windows操作系统是小端存储,是反过来的;我们来翻转一下:

我们打印出来并进行二进制转换的hashCode:  01001000 01010011 00111110 01100100

JOL打印出来的object header中的hashCoxe:    01001000 01010011 00111110 01100100

一样的吧,说明官方文档中说是对的;

最后来研究一下mark word中最后8bit,也是最终的8bit;

unused:1 age:4 biased_lock:1 lock:2

分别存储GC分代年龄,偏向锁标识,对象状态;见下图;

JOL打印中的最后8bit: 00000001

        0                   0000              0               01

没有被使用      GC分代年龄    偏向标识    对象状态

我们着重分析一下最后三位,其中最后两位是用于标识对象状态,也就是锁状态,Java中的对象状态有:无锁,偏向锁,轻量锁,重量锁,GC标识五种状态;

那么最后两位最多组合只能表示4中状态:00,01,10,11,JVM怎么表示五种状态呢?

答案就是JVM把偏向锁和无锁表示为同一种状态,随后通过偏向标识,来区分偏向锁和无锁,当偏向锁的时候,偏向标识是1,对象状态是01,下面会具体分析;

因为上面代码中并没有对对象A进行加锁操作,所以打印出来的是无锁状态,可以判定,在无锁时的mark_word中对象头的偏向标识是不可偏向0,且对象状态是01,代表无锁;


public class JOLExample {

    static A a ;

    public static void main(String[] args) throws InterruptedException {

        a = new A();

        out.println("对象A加锁之前。。。"); //预期应该是无锁

        out.println(ClassLayout.parseInstance(a).toPrintable());

        sync();

        out.println("对象A加锁之后。。。"); //预期应该是偏向锁

        out.println(ClassLayout.parseInstance(a).toPrintable());

    }

    public static void sync(){

        synchronized (a){

            out.println("对象A加锁中 ing 。。。"); //预期应该是无锁

            out.println(ClassLayout.parseInstance(a).toPrintable());

        }

    }

}

结果:

对象A加锁之前。。。

sync.demo.A 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)                           18 0a f0 17 (00011000 00001010 11110000 00010111) (401607192)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对象A加锁中 ing 。。。

sync.demo.A object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           30 f1 91 03 (00110000 11110001 10010001 00000011) (59896112)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           18 0a f0 17 (00011000 00001010 11110000 00010111) (401607192)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对象A加锁之后。。。

sync.demo.A 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)                           18 0a f0 17 (00011000 00001010 11110000 00010111) (401607192)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

从上面的打印结果我们可以知道,上面执行的代码,并没有资源竞争,至始至终都是main线程在执行,在Java1.6之前,JVM中synchronized关键字的实现方式是调用操作系统底层的pthread_mutex_t  函数实现加锁,而1.6之后进行了优化,在没有资源竞争的情况下,JVM的加锁应该是偏向锁,但是从结果中看,肯定不是偏向锁,因为偏向标识是0,这里的000是一个轻量锁,因为没有资源竞争,肯定不是重量级锁,那么怎样才能看到偏向锁呢?为什么这里默认就从无锁升级为轻量锁了呢?看下一一段代码

public class JOLExample1 {

    static A a ;

    public static void main(String[] args) throws InterruptedException {

        //延迟5秒

        Thread.sleep(5000);

        a = new A();

        out.println("对象A加锁之前。。。");

        out.println(ClassLayout.parseInstance(a).toPrintable());

        sync();

        out.println("对象A加锁之后。。。");

        out.println(ClassLayout.parseInstance(a).toPrintable());

    }

    public static void sync(){

        synchronized (a){

            out.println("对象A加锁中 ing 。。。");

            out.println(ClassLayout.parseInstance(a).toPrintable());

        }

    }

}

运行结果:

对象A加锁之前。。。

sync.demo.A object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           18 0a c1 17 (00011000 00001010 11000001 00010111) (398527000)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对象A加锁中 ing 。。。

sync.demo.A object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 f8 c6 02 (00000101 11111000 11000110 00000010) (46594053)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           18 0a c1 17 (00011000 00001010 11000001 00010111) (398527000)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对象A加锁之后。。。

sync.demo.A object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 f8 c6 02 (00000101 11111000 11000110 00000010) (46594053)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           18 0a c1 17 (00011000 00001010 11000001 00010111) (398527000)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

打印结果出来了,有没有被吓到,101,偏向锁;

两个疑问:

1.为什么延迟5秒,执行同步代码块时是偏向锁?

2.为什么对象一开始就已经是偏向锁,不应该是无锁吗?

再看一个运行结果:在JVM执行参数中加入-XX:BiasedLockingStartupDelay=0,一会在解释这个参数有什么作用

运行结果:

对象A加锁之前。。。

sync.demo.A object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           18 0a dd 17 (00011000 00001010 11011101 00010111) (400362008)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对象A加锁中 ing 。。。

sync.demo.A object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 f8 c9 02 (00000101 11111000 11001001 00000010) (46790661)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           18 0a dd 17 (00011000 00001010 11011101 00010111) (400362008)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

对象A加锁之后。。。

sync.demo.A object internals:

OFFSET  SIZE   TYPE DESCRIPTION                               VALUE

      0     4        (object header)                           05 f8 c9 02 (00000101 11111000 11001001 00000010) (46790661)

      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)

      8     4        (object header)                           18 0a dd 17 (00011000 00001010 11011101 00010111) (400362008)

     12     4        (loss due to the next object alignment)

Instance size: 16 bytes

Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

上面的代码关闭睡眠,加上了-XX:BiasedLockingStartupDelay=0这个运行参数,为什么对象一开始就是偏向锁了呢?前面例子不是在没有睡眠之前是无锁的呀,为什么现在一上来就变成偏向锁?

-XX:BiasedLockingStartupDelay=0 就是这个参数的原因,这个参数是禁止偏向锁延迟,因为JVM默认是延迟开启偏向锁的,时间大概是4秒左右,那JVM为什么要延迟开启偏向锁呢?那是因为,在JVM启动的时候,JVM认为在JVM启动的时候,JVM的运行程序中的同步代码都是存在资源竞争的,没有必要开启偏向锁的必要,等我JVM启动完了,在开启偏向锁供用户使用。所以就出现了前面的差异;

总结一下Java Object header:

Java对象头一共占12byte,其中mark word占8byte,klass占4byte;

锁状态:

001:无锁

101:偏向锁

000:轻量级锁

010:重量级锁

你可能感兴趣的:(Java Object header对象头分析)