synchronized锁原理分析(一、从Java对象头看synchronized锁的状态)

synchronized关键字"锁"的实现本质

synchronized关键字实现的锁是依赖于JVM的,底层调用的是操作系统的指令集实现。
Lock接口实现的锁不一样,例如ReentrantLock锁是基于JDK实现的,有Java原生代码来实现的。

synchronized 锁的是什么?
Object o = new Object();
synchronized (o){
   System.out.println("执行代码");
}

上面这段代码,synchronized锁的其实是 o 这个对象,不是{}里面的代码块,synchronized (o)加锁的过程可以理解为,通过改变o这个对象的某些标识位来实现的,那么加锁究竟改变了对象的什么呢?

synchronized (o) 会改变o这个对象的对象头信息

那么java对象头是什么?里面都有些什么?

了解一下Java对象的内存结构

先来了解一下java对象结构,在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。下图是64位操作系统下普通对象实例数据结构:
在这里插入图片描述

对象头

HotSpot虚拟机的对象头包括两部分信息:

markword
第一部分markword,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
32位下,长度是32bit也就是4字节
64位下,长度是64bit也就是8字节

这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称为“MarkWord”

klass
对象头的另外一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.
32位下,长度是32bit也就是4字节
64位下(未开启压缩指针),长度是64bit也就是8字节
64位下(开启压缩指针),长度是32bit也就是4字节

length(数组对象才有)
如果是数组对象的话,对象头还有一块表示数组长度的数据,大小是32bit,4个字节

实例数据

实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。

对齐填充

第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

如何通过Java代码查看一个Java对象的内存布局?

这里可以通过 openjdk的一个工具包jol(Java Object Layout)来查看

Maven依赖

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

Java代码,打印a对象的布局

A a = new A();
a.hashCode(); /// 这里获取一下hashCode
System.out.println(ClassLayout.parseInstance(a).toPrintable());

下图就是 对象 a的 内存布局,这里默认开启了指针压缩,所以可以看到 markword是8个字节,klass是4个字节,对象头一共是12字节。
还有一个需要注意的问题就是由于大小端引起的问题,使得这里展示的高低位相反
在这里插入图片描述

根据上图展示我们可以看出,a对象的java头的信息是
mark_word的8个字节(64bit)是:00000000 00000000 00000000 00100101 00000011 11011011 11010011 00000001
klass的4个字节(32bit)是:11111000 00000000 11000000 01000011

解读Java对象头里面的信息

Klass里面存的是一个地址,是一个指向当前对象所属于的类的地址,可以通过这个地址获取到它的元数据信息。

这里重点说一下 Mark Word,这里面主要用于存储对象的运行时记录信息,如哈希值、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。一个对象在处于不同的状态,里面的结构也有不同。

下面是HotSpot中markOop.hpp文件的一段注释


//    [JavaThread* | epoch | age | 1 | 01]       lock is biased toward given thread
//    [0           | epoch | age | 1 | 01]       lock is anonymously biased
//
//  - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
//    [ptr             | 00]  locked             ptr points to real header on stack
//    [header      | 0 | 01]  unlocked           regular object header
//    [ptr             | 10]  monitor            inflated lock (header is wapped out)
//    [ptr             | 11]  marked             used by markSweep to mark an object
//                                               not valid at any other time
//
//    We assume that stack/thread pointers have the lowest two bits cleared.

下面是按照我的理解转换为图片表示
在这里插入图片描述

根据上面可以把对象分为六种状态:无锁不可偏向、无锁可偏向、偏向锁、轻量级锁、重量级锁、被GC标记状态。

判断流程:
看最后两位:
如果是 11表示被GC标记
如果是10表示重量级锁
如果是00表示轻量级锁
如果是01 那么继续看倒数第三位,如果是0,表示无锁,且不可偏向
————————————————如果是1,看前面54bit,如果全是0,表示无锁,但是处于可以偏向状态
————————————————————前面54bit有数据,表示偏向锁,54bit里面存储的就是当前偏向的线程信息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jianyang.liu

从来没收到过一分钱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值