源码分析Java虚拟机中锁膨胀的过程

Java 6 对 synchronized 做了多方面的优化,其中最主要的就是引入了 偏向锁和轻量级锁。锁的获取次序依次是 偏向锁 -> 轻量级锁 -> 重量级锁。

这3种锁的膨胀过程都是在 JVM 源码中的 synchronizer.cpp
实现,具体路径:

jdk7u-hotspot/src/share/vm/runtime/synchronizer.cpp

synchronized 修饰的代码经过编译之后,会在代码前后插入 monitorenter
monitorexit
这两个指令。这两个指令会分别执行到源码的   InterpreterRuntime.cpp
中:

InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)

InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem)

参数解释:

  • JavaThread指向java中的获取锁的当前线程

  • BasicObjectLockBasicObjectLock类型的elem对象 内部包含一个BasicLock对象  _lock
     
    和一个指向Object对象的指针 
    _obj

BasicObjectLock 结构如下图:

源码分析Java虚拟机中锁膨胀的过程

我们从 monitorenter 为入口,沿着偏向锁 -> 轻量级锁 -> 重量级锁的膨胀过程来分析 synchronized 的实现过程。

monitorenter

源码如下:

源码分析Java虚拟机中锁膨胀的过程

图中标识 UseBiasedLocking
是判断JVM是否启动了偏向锁开关,如果打开则调用 fast_enter 获取  偏向锁
;否则调用 slow_enter 获取  轻量级锁

偏向锁

获取偏向锁源码流程

获取偏向锁的操作是在 fast_enter
函数中实现,具体逻辑如下:

源码分析Java虚拟机中锁膨胀的过程


fast_enter

流程说明:

  • 再次检查偏向锁是否开启

  • 当处于不安全点时, 调用 BiasedLocking的 revoke_and_rebias 函数来尝试获取偏向锁
    ,如果成功则直接返回;如果失败则进入轻量级锁获取过程。这个函数具体实现是在 biasedLocking.cpp
    文件中

  • 如果偏向锁未开启,则进入 slow_enter获取轻量级锁的流程

revoke_and_rebias
部分实现逻辑如下:

源码分析Java虚拟机中锁膨胀的过程

解释说明:

  1. 通过markOop mark = obj -> mark() 获取对象的 markOop 数据mark,即对象头中的Mark Word

  2. 判断mark -> has_bias_pattern 判断所对象是否为可偏向状态,也就是 Mark Word 的偏向锁标志位为 1,锁标志位为 01

  3. 判断 Mark Word 中线程ID:如果为空,则进入步骤(4);如果指向当前线程,说明是重入锁,直接执行同步代码块即可;如果指向其它线程,说明当前存在多个线程竞争锁,需要将其升级为轻量级锁

  4. 通过CAS原子指令设置Mark Word 中线程ID为当前线程ID,如果执行CAS成功,则执行同步代码块;否则说明存在多线程竞争锁,需要撤销偏向锁并升级为轻量级锁

获取偏向锁案例

通过工具JOL(Java Object Layout)可以查看Java对象在内存中的具体情况。比如以下代码:

源码分析Java虚拟机中锁膨胀的过程

调用 ClassLayout.parseInstance(testDemo) 方法, 获取锁对象testDemo的内存情况,执行上述代码打印结果如下:

源码分析Java虚拟机中锁膨胀的过程

图中header的前8个字节按照平时习惯的从高位到低位的展示为:

00000000 00000000 00000000 00111001 10101110 11101101 00101111 00000
101

后3位分别代表是否偏向锁和锁标志位,分析图中结果如下:

  • 加锁前:
    101

    虽然是 偏向锁
    ,没有任何线程持有锁(也就是Mark Word中的线程ID为空)


    源码分析Java虚拟机中锁膨胀的过程

  • 加锁中:
    101

    表示 偏向锁
    ,当前main线程拿到锁(也就是Mark Word中的线程ID指向main线程)

    源码分析Java虚拟机中锁膨胀的过程

  • 加锁后:
    101

    表示 偏向锁
    ,表示当前锁对象依然是偏向锁状态(偏向锁退出锁后依然是偏向状态)

    源码分析Java虚拟机中锁膨胀的过程

注意
:在上述代码开始阶段,使用 sleep(5000) 睡眠5秒钟,是因为虚拟机在启动时会有4秒钟的偏向锁延迟,源码如下所示:

路径:openjdk/hotspot/src/share/vm/runtime/globals.hpp


BiasedLockingStartupDelay, 4000 //偏向锁延迟4000ms

product(intx, BiasedLockingStartupDelay, 4000,

"Number of milliseconds to wait before enabling biased locking")

range(0, (intx)(max_jint-(max_jint%PeriodicTask::interval_gran)))

constraint(BiasedLockingStartupDelayFunc,AfterErgo)

轻量级锁

获取轻量级锁源码流程

如果说偏向锁是只允许一个线程获得锁,那么轻量级锁就是允许多个线程获得锁, 但是只允许他们顺序拿锁,不允许出现竞争
,也就是拿锁失败的情况。

轻量级锁的实现在 slow_enter
函数中,具体逻辑如下:

源码分析Java虚拟机中锁膨胀的过程

slow_enter
流程说明:

  • 当前线程的栈帧中创建一个锁记录的空间,这个空间存储对象头中Mark World的拷贝,就是复制一份到这个锁记录空间


    源码分析Java虚拟机中锁膨胀的过程

  • 然后虚拟机使用CAS尝试将这个锁记录空间的指针更新到Mark World,如果CAS操作成功,那么当前线程获取到锁,此时锁状态处于轻量级锁,锁标志位置为00


    源码分析Java虚拟机中锁膨胀的过程

  • 如果CAS失败,则调用 ObjectSynchronizer::inflate 膨胀为重量级锁

获取轻量级锁案例

如下代码:

源码分析Java虚拟机中锁膨胀的过程

分别打印出默认情况(main线程加锁)下和线程 t1 加锁后锁对象testDemo的状态,结果如下:

源码分析Java虚拟机中锁膨胀的过程

可以看出当有其它线程竞争锁对象时,锁对象 Mark Word 中的锁标志为被位置 00。

注意:重量级锁是通过对象内部的监视器(monitor)来实现,而monitor的本质是依赖操作系统底层的MutexLock实现的,这一节不做详细介绍,后续有时间再考虑重写一篇专门分析synchronized重量级锁的实现源码分析。

原文 

http://mp.weixin.qq.com/s?__biz=MzU3Mjc5NjAzMw==&mid=2247484169&idx=1&sn=e8384ea41147f58b40e50b0d0d5ff484

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 源码分析Java虚拟机中锁膨胀的过程

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址