Java对象头分析

准备

对象的几个部分的作用:
1.对象头中的Mark Word(标记字)主要用来表示对象的线程锁状态,另外还可以用来配合GC、存放该对象的hashCode;
2.Klass Word是一个指向方法区中Class信息的指针,意味着该对象可随时知道自己是哪个Class的实例;
3.数组长度也是占用64位(8字节)的空间,这是可选的,只有当本对象是一个数组对象时才会有这个部分;
4.对象体是用于保存对象属性和值的主体部分,占用内存空间取决于对象的属性数量和类型;
5.对齐空间(在64位的虚拟机中,对象的大小为8的倍数)
Mark Word(标记字)
mark word图解
biased_lock:对象是否启用偏向锁标记,只占1个二进制位。为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。lock和biased_lock共同表示对象处于什么锁状态。
age:4位的Java对象年龄。在GC中,如果对象在Survivor区复制一次,年龄增加1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6。由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。
identity_hashcode:31位的对象标识hashCode,采用延迟加载技术。调用方法System.identityHashCode()计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。
thread:持有偏向锁的线程ID。
epoch:偏向锁的时间戳。
ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针。
ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。
在这里插入图片描述

Klass Word(类指针)
这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64位的JVM将会比32位的JVM多耗费50%的内存。为了节约内存可以使用选项+UseCompressedOops开启指针压缩,其中,oop即ordinary object pointer普通对象指针。开启该选项后,下列指针将压缩至32位:

每个Class的属性指针(即静态变量)
每个对象的属性指针(即对象变量)
普通对象数组的每个元素指针
当然,也不是所有的指针都会压缩,一些特殊类型的指针JVM不会优化,比如指向PermGen的Class对象指针(JDK8中指向元空间的Class对象指针)、本地变量、堆栈元素、入参、返回值和NULL指针等。
在pom文件中引入openjdk.jol的jar包

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

基本使用

    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 20 (11100101 00000001 00000000 00100000) (536871397)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes

o对象的总的大小是16B,对象头(object header)12B,然后4B是用来对其使用,hotsopt64位虚拟机规定对象的大小是8的倍数。
当对象由对应的字段的时候

    private boolean flag =true;

    public static void main(String[] args) {

        Test1 test1 = new Test1();
        System.out.println(ClassLayout.parseInstance(test1).toPrintable());
    }

执行结果

com.suning.com.Test1 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)                           05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
     12     1   boolean Test1.flag                                true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

boolean占用了1个bit,然后空位补齐3bit。刚好对象的大小为16B.
由上,对象的组成是由三部分组成,分别是对象头(object header)、对象实例数据、数据对齐。

对象头(object header)分析

Test1 test1 = new Test1();
       //16进制转化
       System.out.println("JVM------0X"+Integer.toHexString(test1.hashCode()));
       System.out.println("二进制转化----"+Integer.toBinaryString(test1.hashCode()));
       //自己封装hash算法后转化
       HashUtil.countHash(test1);
       System.out.println(ClassLayout.parseInstance(test1).toPrintable());

当计算对象的hashCode值之后在对对象布局输出
执行结果

JVM------0X1540e19d
二进制转化----10101010000001110000110011101
util‐‐‐‐‐‐‐‐‐‐‐0x1540e19d
com.suning.com.Test1 object internals:
OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
     0     4           (object header)                           01 9d e1 40 (00000001 10011101 11100001 01000000) (1088527617)
     4     4           (object header)                           15 00 00 00 (00010101 00000000 00000000 00000000) (21)
     8     4           (object header)                           05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
    12     1   boolean Test1.flag                                true
    13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

由hotspots中的注释可以知道在对象头的64个bit当中
unused:25 hash 31 ->| unused 1 age:4 biased_lock:1 lock:2
未使用的占25个字节,由于电脑cpu是采用了小端存储,所以是从后向前排列
首先00000000 00000000 00000000是未使用的
其次10101010000001110000110011101是hashCode的二进制存储;
然后是年龄占4位,这也就解释了JVM在GC的时候,为什么年轻代的年龄到达15后会移至老年代。(4位2进制最大可以表示到15)。
那么首位的8个字节。首先第一个没有使用
0(未使用) 0000(分代年龄)0(偏向锁,1是偏向)01(锁状态)
Java的对象头在不同的状态下会有不同的表现形式,主要的表现形式有三种:无锁状态、加锁状态、gc标记状态。在加锁状态又有偏向锁、轻量锁、重量锁三种锁状态。
由上分析可知01的状态是无锁状态。

Test1 test1 = new Test1();
        System.out.println("锁之前");
        System.out.println(ClassLayout.parseInstance(test1).toPrintable());
        synchronized (test1){
            System.out.println("锁对象");
            System.out.println(ClassLayout.parseInstance(test1).toPrintable());
        }
        System.out.println("锁之后");
        System.out.println(ClassLayout.parseInstance(test1).toPrintable());

执行结果

锁之前
com.suning.com.Test1 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)                           05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
     12     1   boolean Test1.flag                                true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

锁对象
com.suning.com.Test1 object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           18 f5 65 02 (00011000 11110101 01100101 00000010) (40236312)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
     12     1   boolean Test1.flag                                true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

锁之后
com.suning.com.Test1 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)                           05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
     12     1   boolean Test1.flag                                true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total


Process finished with exit code 0

由上可以看出来,000是轻量级锁。

   //在开始执行之前。先睡眠5秒钟,即可触发对象的偏向锁
	Thread.sleep(5000);

        Test1 test1 = new Test1();
        System.out.println("锁之前");
        System.out.println(ClassLayout.parseInstance(test1).toPrintable());
        synchronized (test1){
            System.out.println("锁对象");
            System.out.println(ClassLayout.parseInstance(test1).toPrintable());
        }
        System.out.println("锁之后");
        System.out.println(ClassLayout.parseInstance(test1).toPrintable());

执行结果

锁之前
com.suning.com.Test1 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)                           05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
     12     1   boolean Test1.flag                                true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

锁对象
com.suning.com.Test1 object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 30 18 03 (00000101 00110000 00011000 00000011) (51916805)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
     12     1   boolean Test1.flag                                true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

锁之后
com.suning.com.Test1 object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 30 18 03 (00000101 00110000 00011000 00000011) (51916805)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
     12     1   boolean Test1.flag                                true
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total


Process finished with exit code 0

由上面的执行结果可以看出来
0-未使用 0000-gc年龄 1-偏向锁开启 01-无锁状态。

为什么大于4秒钟的时候才会有偏向锁状态

其实这是因为虚拟机在启动的时候对于偏向锁有延迟。
因为在项目启动的时候,有大量的同步块,多个线程访问的时候,需要消除偏向锁。会很麻烦,反而会降低效率。
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
修改上面的jvm参数,可以开启jvm偏向锁延迟配置,延迟为0
-XX:-UseBiasedLocking关闭偏向锁

偏向锁、轻量级锁、重量级锁的概念

Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。

轻量级锁:如果成功使用CAS将对象头重的Mark Word替换为指向锁记录的指针,则获得锁,失败则当前线程尝试使用自旋(循环等待)来获取锁。
当有另一个线程与该线程同时竞争时,锁会升级为重量级锁。为了防止继续自旋,一旦升级,将无法降级。
重量级锁:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程,进行竞争。有操作内核os参与。
重量级锁展示

 static A a;

    public static void main(String[] args) throws Exception {
        //Thread.sleep(5000);
        a = new A();
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        Thread t1 = new Thread() {
            public void run() {
                synchronized (a) {
                    try {
                        synchronized (a) {
                            System.out.println("before wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                            a.wait();
                            System.out.println(" after wait");
                            out.println(ClassLayout.parseInstance(a).toPrintable());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(5000);
        synchronized (a) {
            a.notifyAll();
        }
    }

    public static void sync() throws InterruptedException {
        synchronized (a) {
            System.out.println("t1 main lock");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }
    }

执行结果:

befre lock
com.lock.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)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

before wait
com.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           90 f4 e9 19 (10010000 11110100 11101001 00011001) (434762896)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

 after wait
com.lock.A object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           3a b4 a5 02 (00111010 10110100 10100101 00000010) (44414010)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           9f c1 00 20 (10011111 11000001 00000000 00100000) (536920479)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

当执行了hashCode计算之后,就没有偏向锁了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值