在说synchronized内置锁之前,首先先来研究一下对象头,因为所谓锁便是给对象一个标识,而这个标识便是存在对象头当中。
对象:对象头 + 实例数据 + 填充数据
对象头:markword + classpoint
一、对象头
接下来看下面的一张图
意思是java的对象头在对象的不同状态下会有不同的表现形式,主要有三种状态,无锁状态、加锁状
态、gc标记状态。那么我可以理解java当中的取锁其实可以理解是给对象上锁,也就是改变对象头的状
态,如果上锁成功则进入同步代码块。但是java当中的锁有分为很多种,从上图可以看出大体分为偏向
锁、轻量锁、重量锁三种锁状态。这三种锁的效率完全不同,我们只有合理的设计代码,才能合理的利
用锁、那么这三种锁的原理是什么?所以我们需要先研究这个对象头。
二、java对象的布局以及对象头
1.JOL来分析java的对象布局
首先maven添加JOL的依赖
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId>5 <version>0.9</version> </dependency>
A.java 源码
package com.enjoy.entity; public class A { }
JOLExample1.java
package com.enjoy.test;
import com.enjoy.entity.A;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class Test1 { static A a= new A();
public static void main(String[] args) {
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseClass(A.class).toPrintable());
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果:
分析结果1
整个对象一共16B,其中对象头(Object header)12B,还有4B是对齐的字节(因为在64位虚拟机上
对象的大小必须是8的倍数),由于这个对象里面没有任何字段,故而对象的实例数据为0B;两个问题
1、什么叫做对象的实例数据呢?
2、那么对象头里面的12B到底存的是什么呢?
首先要明白什么对象的实例数据很简单,我们可以在A当中添加一个boolean的字段,大家都知道
boolean字段占1B,然后再看结果
A.java
package com.enjoy.entity; public class A { boolean f; }
运行结果2
分析结果2
整个对象的大小还是没有改变一共16B,其中对象头(Object header)12B,boolean字段f(对象的
实例数据)占1B、剩下的3B就是对齐字节。由此我们可以认为一个对象的布局大体分为三个部分分别
是对象头(Object header)、对象的实例数据字节对齐;
对象布局:
接下来讨论第二个问题,对象头为什么是12B?这个12B当中分别存储的是什么呢?
2、大小端模式
再接着往下研究对象的时候先要高明白大小端存储,一般家用笔记本都是小端模式,那么什么是小端模式呢?
高字节存在高地址,低字节存在低地址
其实说白了它就是倒着存储的,比如int a = 1占4个字节,转化成二进制就是00000000 00000000 00000000 00000001,小端存储就是
00000001 00000000 00000000 00000000。
3、对象头的规范
link.
首先引用openjdk文档当中对对象头的解释
上述引用中提到一个java对象头包含了2个word,并且好包含了堆对象的布局、类型、GC状态、同步状
态和标识哈希码,具体怎么包含的呢?又是哪两个word呢?
对象头的结构大概如下图
mark word为第一个word根据文档可以知他里面包含了锁的信息,hashcode,gc信息等等,第二个word是什么呢?
klass word为对象头的第二个word主要指向元空间中对象的数据,他是一个指针;
假设我们理解一个对象头主要上图两部分组成(数组对象除外,数组对象的对象头还包含一个数组长度),那么一个java的对象头多大呢?我们从JVM的源码注释中得知到一个mark word一个是64bit,那么klass的长度是多少呢?(12-8=4byte)所以我们需要想办法来获得java对象头的详细信息,验证一下他的大小,验证一下里面包含的信息是否正确。
根据上述利用JOL打印的对象头信息可以知道一个对象头是12B,其中8B是mark word 那么剩下的4B就 是klass word了,和锁相关的就是mark word了,那么接下来重点分析mark word里面信息
在无锁的情况下markword当中的前56bit存的是对象的hashcode,那么来验证一下
java代码
public class Test1 { static A a= new A();
//-XX:BiasedLockingStartupDelay=0 public static void main(String[] args) throws InterruptedException {
log.debug("----hashcode before");
log.debug(ClassLayout.parseInstance(a).toPrintable()); //转化成16进制,方便比较
log.debug(Integer.toHexString(a.hashCode()));
log.debug("----hashcode after"); //计算完hashcode之后的a对象的布局
log.debug(ClassLayout.parseInstance(a).toPrintable());
}
}
运行结果
分析结果3:
没有进行hashcode之前的对象头信息,可以看到2b-8b之前的的56bit是没有值,打印完hashcode之后行就有值了,为什么是2-8B,不应该是1-7B呢?因为是小端存储。那么第一个字节当中的八位分别存的就是分带年龄、偏向锁信息,和对象状态,这个8bit分别表示的信息如下图;这个图会随着对象状态改变,下图是无锁状态下
其中的是否可偏向标识在无锁情况下会根据是否计算hashcode而变化;因为如果计算了hashcode之后对象便变得不可偏向;为什么?
关于对象状态一共分为五种状态,分别是无锁不可偏向、无锁可向锁、轻量锁、重量锁、GC标记,那么2bit,如何能表示五种状态(2bit最多只能表示4中状态分别是:01,00,10,11),jvm做的比较好的是把是否可偏向表示为一个状态,然后根据图中偏向锁的标识再去标识是可偏向的还是不可偏向的