本文为synchronized系列第二篇。主要内容为分析偏向锁的实现。
偏向锁的诞生背景和基本原理在上文中已经讲过了,强烈建议在有看过上篇文章的基础下阅读本文。
本文将分为几块内容:
1.偏向锁的入口
2.偏向锁的获取流程
3.偏向锁的撤销流程
4.偏向锁的释放流程
5.偏向锁的批量重偏向和批量撤销
本文分析的JVM版本是JVM8,具体版本号以及代码可以在这里看到。
偏向锁入口
目前网上的很多文章,关于偏向锁源码入口都找错地方了,导致我之前对于偏向锁的很多逻辑一直想不通,走了很多弯路。
synchronized
分为synchronized
代码块和synchronized
方法,其底层获取锁的逻辑都是一样的,本文讲解的是synchronized
代码块的实现。上篇文章也说过,synchronized
代码块是由monitorenter
和monitorexit
两个指令实现的。
关于HotSpot虚拟机中获取锁的入口,网上很多文章要么给出的方法入口为interpreterRuntime.cpp#monitorenter,要么给出的入口为bytecodeInterpreter.cpp#1816。包括占小狼的这篇文章关于锁入口的位置说法也是有问题的(当然文章还是很好的,在我刚开始研究synchronized
的时候,小狼哥的这篇文章给了我很多帮助)。
要找锁的入口,肯定是要在源码中找到对monitorenter
指令解析的地方。在HotSpot的中有两处地方对monitorenter
指令进行解析:一个是在bytecodeInterpreter.cpp#1816 ,另一个是在templateTable_x86_64.cpp#3667。
前者是JVM中的字节码解释器(bytecodeInterpreter
),用C++实现了每条JVM指令(如monitorenter
、invokevirtual
等),其优点是实现相对简单且容易理解,缺点是执行慢。后者是模板解释器(templateInterpreter
),其对每个指令都写了一段对应的汇编代码,启动时将每个指令与对应汇编代码入口绑定,可以说是效率做到了极致。模板解释器的实现可以看这篇文章,在研究的过程中也请教过文章作者‘汪先生’一些问题,这里感谢一下。
在HotSpot中,只用到了模板解释器,字节码解释器根本就没用到,R大的读书笔记中说的很清楚了,大家可以看看,这里不再赘述。
所以montorenter
的解析入口在模板解释器中,其代码位于templateTable_x86_64.cpp#3667。通过调用路径:templateTable_x86_64#monitorenter
->interp_masm_x86_64#lock_object
进入到偏向锁入口macroAssembler_x86#biased_locking_enter
,在这里大家可以看到会生成对应的汇编代码。需要注意的是,不是说每次解析monitorenter
指令都会调用biased_locking_enter
,而是只会在JVM启动的时候调用该方法生成汇编代码,之后对指令的解析是通过直接执行汇编代码。
其实bytecodeInterpreter
的逻辑和templateInterpreter
的逻辑是大同小异的,因为templateInterpreter
中都是汇编代码,比较晦涩,所以看bytecodeInterpreter
的实现会便于理解一点。但这里有个坑,在jdk8u之前,bytecodeInterpreter
并没有实现偏向锁的逻辑。我之前看的JDK8-87ee5ee27509这个版本就没有实现偏向锁的逻辑,导致我看了很久都没看懂。在这个commit中对bytecodeInterpreter
加入了偏向锁的支持,我大致了看了下和templateInterpreter
对比除了栈结构不同外,其他逻辑大致相同,所以下文就按bytecodeInterpreter中的代码对偏向锁逻辑进行讲解。templateInterpreter
的汇编代码讲解可以看这篇文章,其实汇编源码中都有英文注释,了解了汇编几个基本指令的作用再结合注释理解起来也不是很难。
偏向锁获取流程
下面开始偏向锁获取流程分析,代码在bytecodeInterpreter.cpp#1816。注意本文代码都有所删减。
CASE(_monitorenter): { // lockee 就是锁对象 oop lockee = STACK_OBJECT(-1); // derefing's lockee ought to provoke implicit null check CHECK_NULL(lockee); // code 1:找到一个空闲的Lock Record BasicObjectLock* limit = istate->monitor_base(); BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base(); BasicObjectLock* entry = NULL; while (most_recent != limit ) { if (most_recent->obj() == NULL) entry = most_recent; else if (most_recent->obj() == lockee) break; most_recent++; } //entry不为null,代表还有空闲的Lock Record if (entry != NULL) { // code 2:将Lock Record的obj指针指向锁对象 entry->set_obj(lockee); int success = false; uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place; // markoop即对象头的mark word markOop mark = lockee->mark(); intptr_t hash = (intptr_t) markOopDesc::no_hash; // code 3:如果锁对象的mark word的状态是偏向模式 if (mark->has_bias_pattern()) { uintptr_t thread_ident; uintptr_t anticipated_bias_locking_value; thread_ident = (uintptr_t)istate->thread(); // code 4:这里有几步操作,下文分析 anticipated_bias_locking_value = (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) & ~((uintptr_t) markOopDesc::age_mask_in_place); // code 5:如果偏向的线程是自己且epoch等于class的epoch if (anticipated_bias_locking_value == 0) { // already biased towards this thread, nothing to do if (PrintBiasedLockingStatistics) { (* BiasedLocking::biased_lock_entry_count_addr())++; } success = true; } // code 6:如果偏向模式关闭,则尝试撤销偏向锁 else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) { markOop header = lockee->klass()->prototype_header(); if (hash != markOopDesc::no_hash) { header = header->copy_set_hash(hash); } // 利用CAS操作将mark word替换为class中的mark word if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) { if (PrintBiasedLockingStatistics) (*BiasedLocking::revoked_lock_entry_count_addr())++; } } // code 7:如果epoch不等于class中的epoch,则尝试重偏向 else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) { // 构造一个偏向