介绍偏向锁之前,先熟悉一下对象在堆中的数据结构。数组需要有一块区域表示数组的长度,对象和数组的结构是有区别的。
对象主要分为对象头、对象中实际的数据和对齐填充。数组分为对象头、数组中的元素和对齐填充,数组的长度在对象头中存储,图示如下:
Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。
它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
Class Pointer:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
下图是32虚拟机表示的不同锁的状态下Mark Word中的数据结构 。
- 无状态也就是无锁的时候,对象头开辟 25bit 的空间用来存储对象的 hashcode ,4bit 用于存放分代年龄,1bit 用来存放是否偏向锁的标识位,2bit 用来存放锁标识位为01
- 偏向锁 中划分更细,还是开辟25bit 的空间,其中23bit 用来存放线程ID,2bit 用来存放 epoch,4bit 存放分代年龄,1bit 存放是否偏向锁标识, 0表示无锁,1表示偏向锁,锁的标识位还是01
- 轻量级锁中直接开辟 30bit 的空间存放指向栈中锁记录的指针,2bit 存放锁的标志位,其标志位为00
- 重量级锁中和轻量级锁一样,30bit 的空间用来存放指向重量级锁的指针,2bit 存放锁的标识位,为11
- GC标记开辟30bit 的内存空间却没有占用,2bit 空间存放锁标志位为11。
偏向锁
偏向锁目的是清除数据在无竞争情况下的同步,进一步提高程序的运行性能。轻量级锁是在无竞争的情况下使用CAS操作去清除同步使用的互斥量,而偏向锁是在无竞争情况下把整个同步都清除掉,连CAS都不做了。
获取过程:当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的锁标志位设为01,同时使用CAS操作把获取到这个锁的线程ID记录到对象的Mark Word中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不进行任何同步操作。
释放过程:当有另外一个线程去尝试获取这个锁时,偏向锁就宣告结束,升级为轻量级锁。
轻量级锁
“轻量级”是相对于传统的使用互斥量来实现的传统锁而言的,因此传统的锁机制就被称为“重量级锁”。
获取过程:代码进入同步块的时候,如果此同步对象没有被锁定(锁标示为01状态),虚拟机将使用CAS操作尝试将对象的Mark Work更新为指向Lock Record的指针。如果更新成功,那么这个想成就拥有了这个对象的锁,并且将锁标识位变为00,如果更新失败了,虚拟机首先会检查对象的Mark Word是否指向当先线程的栈帧,如果是说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个线程已经被其他线程抢占了。
释放过程:如果有两条以上的线程同时竞争同一把锁,那么轻量级锁就不在有效,膨胀为重量级锁,锁的标识位变为10。