JVM锁简介:偏向锁、轻量级锁和重量级锁

转自:https://www.aimoon.site/blog/2018/05/21/biased-locking/

 比较复杂,简略见另一篇:https://www.cnblogs.com/twoheads/p/10148598.html

JVM中的java对象头

注意:在没有特殊说明的情况下,都是32 bits为例。

上一小节主要介绍了java中synchronized关键字的使用方法,而在这一小节中将介绍一下synchronized 在JVM中的实现基础——java对象头中的Mark Word

表1   Java对象头的长度

内容说明备注
Mark Word存储对象的Mark Word信息-
Class Metadata Address存储指向对象存储类型的指针-
Array Length数组的长度只有数组对象有该属性

synchronized使用的锁是存放在Java对象头中的Mark Word中,OpenJDK中的markOop.hpp 头文件详细介绍了Mark Word的内容,下面将分析32 bits的JVM中的Mark Word的构成。

表2   32位JVM的Mark Word存储结构

锁状态23 bits2 bits4 bits1 bit2 bits
轻量级锁指向栈中锁记录的指针00
无锁状态hash code分代年龄001
偏向锁Thread IDepoch分代年龄101
重量级锁指向监视器(monitor)的指针10
GC标记011

:最后两位为锁标记位,倒数第三位是偏向标记,如果是1表示是偏向锁;合并单元格的位数就是 该字段的位数,例如hash code共25(23+2)位。

另外,对于偏向锁,如果Thread ID = 0,表示未加锁

JVM锁的类型及对比

Java 1.6对synchronized进行了大幅度的优化,其性能也有了大幅度的提升。Java 1.6引入 “偏向锁”和“轻量级锁”的概念,减少了获得锁和释放锁的消耗。在Java 1.6之后,加上原有的重量 级锁,锁一共有4种状态,分别是:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态。锁只能 按照上述的顺序进行升级操作,锁只要升级之后,就不能降级。

下面将分别介绍一下偏向锁、轻量级锁和重量级锁,并探索一下偏向锁升级为轻量级锁(revoke bias) 的流程和轻量级锁升级为重量级锁(inflate)的流程。偏向锁、轻量级锁的状态转化及对象 Mark Word的关系如下图所示。图片来源:Synchronization and Object Locking文章中的配图

图1   偏向锁、轻量级锁的状态转化及对象Mark Word的关系

1. 偏向锁

偏向锁是Java 1.6新添加的内容,并且是jdk默认启动的选项,可以通过-XX:-UseBiasedLocking 来关闭偏向锁。另外,偏向锁默认不是立即就启动的,在程序启动后,通常有几秒的延迟,可以通过命令 -XX:BiasedLockingStartupDelay=0来关闭延迟。

如果JVM支持偏向锁,那么将按照下图所示的流程分配对象,加偏向锁。图片来源:Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing第3页的配图

图2   偏向锁中的Mark Word的状态转化图

注意:这是简化版的流程图,因为偏向锁的图中缺少了epoch字段。

1.1 偏向锁的加锁

如果JVM支持偏向锁,那么在分配对象时,分配一个可偏向而未偏向的对象(Mark Word的最后3位 为101,并且Thread ID字段的值为0)。

然后,当一个线程访问同步块并获取锁时,将通过CAS(Compare And Swap)来尝试将对象头中的 Thread ID字段设置为自己的线程号,如果设置成功,则获得锁,那么以后线程再次进入和退出 同步块时,就不需要使用CAS来获取锁,只是简单的测试一个对象头中的Mark Word字段中是否存储 着指向当前线程的偏向锁;如果使用CAS设置失败时,说明存在锁的竞争,那么将执行偏向锁的撤销操作 (revoke bias),将偏向锁升级为轻量级锁。

:代码请查看biasedLocking.cpp中的revoke_and_rebias方法

1.2 偏向锁的升级

下面结合代码(有缩减)分析一下偏向锁升级为轻量级锁的过程,这里暂时不考虑批量撤销偏向 (bulk revocation)的情况。详细代码请查看biasedLocking.cpp中的revoke_bias方法。 偏向锁的撤销操作需要在全局检查点(global safepoint)执行,在全局检查点上没有 线程执行字节码。

:偏向锁的撤销的入口函数是biasedLocking.cpp中的revoke方法, 之后会通过VMThread调用revoke_bias方法

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) { markOop mark = obj->mark(); // 检查是否可偏向 if (!mark->has_bias_pattern()) { return BiasedLocking::NOT_BIASED; } uint age = mark->age(); markOop biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age); markOop unbiased_prototype = markOopDesc::prototype()->set_age(age); JavaThread* biased_thread = mark->biased_locker(); if (biased_thread == NULL) { // 可偏向但是未偏向的情况 // 可能的使用场景为:因计算hash code而撤销偏向 if (!allow_rebias) { obj->set_mark(unbiased_prototype); } return BiasedLocking::BIAS_REVOKED; } // 判断对象现在偏向的线程是否还存在 // 即对象头中Mark Word中Thread ID字段指向的线程是否存在 bool thread_is_alive = false; if (requesting_thread == biased_thread) { // 请求的线程拥有偏向锁 thread_is_alive = true; } else { // 请求的线程不拥有偏向锁,递归查询 for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) { if (cur_thread == biased_thread) { thread_is_alive = true; break; } } } if (!thread_is_alive) { if (allow_rebias) { obj->set_mark(biased_prototype); } else { obj->set_mark(unbiased_prototype); } return BiasedLocking::BIAS_REVOKED; } // 拥有偏向锁的线程仍然存活 // 检查该线程是否拥有锁: // 如果拥有锁,那么需要升级为轻量级锁,然后将displaced mark word复制到线程栈中; // 如果不再拥有锁,如果允许重偏向,那么将mark word中的Thread ID 重新置0; // 如果不允许重偏向,那么将mark work设置为无锁状态,即最后两位为01 // cached_monitor_info 是该线程拥有的锁对象的信息,按照从加锁顺序的逆序排列 GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread); BasicLock* highest_lock = NULL; for (int i = 0; i < cached_monitor_info->length(); i++) { MonitorInfo

转载于:https://www.cnblogs.com/twoheads/p/10150063.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值