☕【JVM 技术探索】深入分析各种锁(锁膨胀)运作流程
前提承接
针对于之前两篇关于 synchronized 的文章,主要介绍关于 synchronized 在字节码中的实现方式和表现形式(同步代码块和同步方法)Java并发编程专题系列之深入分析synchronized(基础篇)以及先关针对于同步锁的实现原理比如 ObjectMonitor 的数据结构以及等待队列和同步队列等Java并发编程专题系列之深入分析synchronized(进阶篇),本文主要介绍针对于在执行角度包含对象锁的结构(标记字段的状态)和相关加锁过程进行相关的流程梳理以及对前两章的总结。
synchronized 关键字
synchronized 的锁机制的主要优势是 Java 语言内置的锁机制,因此,JVM 可以自由的优化而不影响已存在的代码。
任何对象都拥有对象头这一数据结构来支持锁,但是对于较大的对象系统开销会更大一些。
java 中的每一个对象都至少包含 2 个字(2 * 4 Bytes for 32bits & 2 * 8 Bytes for 64bits, 不包括已压缩的对象)。
第一个字被称为 Mark Word。这是一个对象的头,它包含了不同的信息,包括锁的相关信息(32bitOS->8bit 大小、64bitOS->16bit 大小)。
第二个字是指向 metadata class 的指针,metadata class 字义了对象的类型。这部分也包含了 VMT(Virtual Method Table)。
Mark Word 的结构如下所示:
Mark Word 根据最低两位(Tag)的所表示的对象状态(包含了锁的类型),编码了不同的信息。
如果这个对象没有被用作锁,Mark Word 记录了 hashcode 和对象年龄(for GC/survivors)。
除此之外,有 3 种状态对应锁:轻量级锁,重量级锁和偏向锁。
轻量级锁
现代 JVM 都引入了轻量级锁
避免将每个对象关联操作系统的 mutex/condition 变量(重量级锁)
当不存在锁竞争时,使用原子操作来进入退出同步块
如果发生锁竞争,回退到操作系统的重量级锁
引入轻量级锁会提供锁效率,因为大部分锁都不存在竞争。
轻量级锁的加锁过程
当一个对象被锁定时,mark word 被复制到当前尝试获取锁的线程的线程栈(Execution stack)的锁记录空间(lock record), 被复制的 mark word 官方称为 displaced mark。
使用 CAS 操作来尝试使 mark word 指向当前线程的锁记录空间(即在 mark word 中存入使用当前线程锁记录空间的指针——stack pointer)。
如果 CAS 操作成功,则线程获得锁。
如果 CAS 操作失败,即存在锁竞争,则发生锁膨胀,回退到重量级锁。
锁记录空间中记录了被当前执行方法锁定的对象(通过遍历线程栈找到线程的锁对象)。
轻量级锁加锁前:
轻量级锁加锁后:
经量级锁的解锁过程:
解锁使用 CAS 来把
displaced mark
写回对象的 mark word 中。如果 CAS 失败, 表示发生锁竞争:则锁膨胀。(通知其他等待线程锁已释放)将锁记录空间置为 0;
如果发生锁膨胀,则用 0 替换
displaced mark
,如果不存在竞争,则 CAS 将锁记录空间置为 0 后,停止 CAS 操作。
偏向锁
偏向锁的引入
在多处理器上 CAS 操作可能开销很大。
大多数锁不仅不存在竞争,而且往往由同一个线程使用。
使单独一个线程获取锁的开销更低。
代价是使另一个线程获取锁开销增大。
偏向锁加锁过程
锁对象第一次被线程获取时,VM 把对象头中的标志位设为 101,即偏向模式。
同时使用 CAS 把获取到这个锁的线程 ID 记录在对象的 mark word 中,如果 CAS 成功,则持有偏向锁的线程以后每次进行这个锁相关的同步块时,不再进行任务同步操作,只进行比较 Mark word 中的线程 ID 是否是当前线程的 ID。
偏向锁的解锁过程
当另外一个线程去尝试获取这个锁时,偏向模式结束。根据锁对象目前是否处于被锁定状态,撤销偏向后恢复到未锁定或轻量级锁定状态。
VM 会停止持有偏向锁的线程(实际上,VM 不能停止单一线程,而是在安全点进行的操作)。
遍历持有偏向锁的线程的栈,找到锁记录空间,将 displaced mark 写入到最旧的锁记录空间,其他的写 0。
更新锁对象的 mark word。如果被锁定,则指向最旧的锁记录空间,否则,填入未锁定值。
偏向锁的特点:
偏向于第一个获取锁的线程:
在 mark word 的 Tag 中增加一位
通过 CAS 来获取偏向锁
对于持有锁的线程接下的锁获取和释放开销非常小(仅仅判断下,不需要 CAS 同步操作)。
如果另一个线程锁定了偏向锁对象,则偏向锁收回,升级为轻量级锁(增加了另一个线程获取锁的开销)。
评论