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里面存储的就是当前偏向的线程信息