synchronized原理之对象头

如果你能模拟一把锁,你就大概知道了锁其实很简单,就是一个对象里面用了一个变量来标识是否自由状态。所谓加锁就是改变这个对象当中的这个变量 ;如:status=1,所谓解锁就是把这个变量的值改为0,如果加锁成功就正常返回,如果加锁不成功就死循环或者阻塞。

对象头的信息分析

已经自己实现了一把锁,那么关于Java当中的内置锁synchronized是怎么实现的?

首先为什么我要去研究Java的对象头,因为所谓锁便是给对象一个标识;而这个标识便是存在对象头当中。这里截取一张hotspot的源码当中的注释:

这张图换成可读的表格如下:

意思是Java对象的对象头在对象的不同状态下会有不同的表现形式,主要有三种状态:无锁状态、加锁状态、gc标记状态。那么我可以理解Java当中的取锁其实可以理解是给对象上锁,也就是改变对象头的状态,如果上锁成功则进入同步代码块。但是Java当中的锁有分为很多种,从上图可以看出大体分为偏向锁、轻量锁、重量锁三种锁状态。这三种锁的效率完全不同,我们只有合理的设计代码,才能合理的利 用锁、那么这三种锁的原理是什么?所以我们需要先研究这个对象头。

JOL分析Java的对象布局

jol的maven依赖:

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

测试:

public class Test {
    static Person person = new Person();
    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());
    }
}
public class Person {
}

分析结果:
整个对象一共16B,其中对象头(Object header)12B,还有4B是对齐的字节(因为在64位虚拟机上对象的大小必须是8的倍数),由于这个对象里面没有任何字段,故而对象的实例数据为0B。两个问题:
1、什么叫做对象的实例数据呢?
2、那么对象头里面的12B到底存的是什么呢?

首先要明白什么对象的实例数据很简单,我们可以在Person当中添加一个boolean的字段,大家都知道boolean字段占1B,然后再看结果:

整个对象的大小还是没有改变一共16B,其中对象头(Object header)12B,boolean字段f(对象的实例数据)占1B、剩下的3B就是对齐字节。由此我们可以认为一个对象的布局大体分为三个部分分别是对象头(Object header)、对象的实例数据字节对齐

接下来讨论第二个问题,对象头为什么是12B?这个12B当中分别存储的是什么呢?(不同位数的VM对象头的长度不一样,这里指的是64bit的vm)

大小端模式

再接着往下研究对象的时候先要高明白大小端存储,一般家用笔记本都是小端模式,那么什么是小端模式呢?
高字节存在高地址,低字节存在低地址

对象头的规范

首先引用openjdk文档当中对对象头的解释: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里面信息在无锁的情况下mark word当中的前56bit存的是对象的hashcode,那么来验证一下:

//-XX:BiasedLockingStartupDelay=0
public static void main(String[] args) throws InterruptedException { 
    log.debug("----hashcode before");
    log.debug(ClassLayout.parseInstance(person).toPrintable());
    //转化成16进制,方便比较
    log.debug(Integer.toHexString(person.hashCode()));
    log.debug("----hashcode after");
    //计算完hashcode之后的a对象的布局
    log.debug(ClassLayout.parseInstance(person).toPrintable());
}

没有进行hashcode之前的对象头信息,可以看到2-8B之前的的56bit是没有值,打印完hashcode之后就有值了,为什么是2-8B,不应该是1-7B呢?因为是小端存储。那么第一个字节当中的八位分别存的就是分带年龄、偏向锁信息,和对象状态,这个8bit分别表示的信息如下图。这个图会随着对象状态改变 而改变,下图是无锁状态下:

其中的是否可偏向标识在无锁情况下会根据是否计算hashcode而变化;因为如果计算了hashcode之后对象便变得不可偏向,为什么?
关于对象状态一共分为五种状态,分别是无锁(偏向锁但是不可偏向)、偏向锁(偏向锁并且可以偏向)、轻量锁、重量锁、GC标记,那么2bit,如何能表示五种状态(2bit最多只能表示4中状态分别是:00,01,10,11),jvm做的比较好的是把是否可偏向表示为一个状态,然后根据图中偏向锁的标识再去标识是可偏向的还是不可偏向的。这里需要注意的一点是:如果没有计算hashcode,对象没加synchronized,此时对象也处于偏向锁状态

00:轻量锁,01:偏向锁,10:重量锁,11:GC标记

关于对象头目前只要了解这些就够了

  • 14
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值