死磕Synchronized底层实现—偏向锁
原文链接:死磕Synchronized底层实现—偏向锁
本文为synchronized系列第二篇。主要内容为分析偏向锁的实现。
偏向锁的诞生背景和基本原理在上文中已经讲过了,强烈建议在有看过上篇文章 的基础下阅读本文 。
更多文章见个人博客:https://github.com/farmerjohngit/myblog
本系列文章将对HotSpot的synchronized
锁实现进行全面分析,内容包括偏向锁、轻量级锁、重量级锁的加锁、解锁、锁升级流程的原理及源码分析,希望给在研究synchronized
路上的同学一些帮助。主要包括以下几篇文章:
死磕Synchronized底层实现—概论
死磕Synchronized底层实现—偏向锁
死磕Synchronized底层实现—轻量级锁
死磕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 。注意本文代码都有所删减 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 CASE (_monitorenter): { oop lockee = STACK_OBJECT (-1 ); CHECK_NULL (lockee); 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++; } if (entry != NULL ) { entry->set_obj (lockee); int success = false ; uintptr_t epoch_mask_in_place = (uintptr_t )markOopDesc::epoch_mask_in_place; markOop mark = lockee->mark (); intptr_t hash = (intptr_t ) markOopDesc::no_hash; if (mark->has_bias_pattern ()) { uintptr_t thread_ident; uintptr_t anticipated_bias_locking_value; thread_ident = (uintptr_t )istate->thread (); anticipated_bias_locking_value = (((uintptr_t )lockee->klass ()->prototype_header () | thread_ident) ^ (uintptr_t )mark) & ~((uintptr_t ) markOopDesc::age_mask_in_place); if (anticipated_bias_locking_value == 0 ) { if (PrintBiasedLockingStatistics) { (* BiasedLocking::biased_lock_entry_count_addr ())++; } success = true ; } 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); } if (Atomic::cmpxchg_ptr (header, lockee->mark_addr (), mark) == mark) { if (PrintBiasedLockingStatistics) (*BiasedLocking::revoked_lock_entry_count_addr ())++; } } else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0 ) { markOop new_header = (markOop) ( (intptr_t ) lockee->klass ()->prototype_header () | thread_ident); if (hash != markOopDesc::no_hash) { new_header = new_header->copy_set_hash (hash); } if (Atomic::cmpxchg_ptr ((void *)new_header, lockee->mark_addr (), mark) == mark) { if (PrintBiasedLockingStatistics) (* BiasedLocking::rebiased_lock_entry_count_addr ())++; } else { CALL_VM (InterpreterRuntime::monitorenter (THREAD, entry), handle_exception); } success = true ; } else { markOop header = (markOop) ((uintptr_t ) mark & ((uintptr_t )markOopDesc::biased_lock_mask_in_place |(uintptr_t )markOopDesc::age_mask_in_place |epoch_mask_in_place)); if (hash != markOopDesc::no_hash) { header = header->copy_set_hash (hash); } markOop new_header = (markOop) ((uintptr_t ) header | thread_ident); DEBUG_ONLY (entry->lock ()->set_displaced_header ((markOop) (uintptr_t ) 0xdeaddead );) if (Atomic::cmpxchg_ptr ((void *)new_header, lockee->mark_addr (), header) == header) { if (PrintBiasedLockingStatistics) (* BiasedLocking::anonymously_biased_lock_entry_count_addr ())++; } else { CALL_VM (InterpreterRuntime::monitorenter (THREAD, entry), handle_exception); } success = true ; } } if (!success) { markOop displaced = lockee->mark ()->set_unlocked (); entry->lock ()->set_displaced_header (displaced); bool call_vm = UseHeavyMonitors; if (call_vm || Atomic::cmpxchg_ptr (entry, lockee->mark_addr (), displaced) != displaced) { if (!call_vm && THREAD->is_lock_owned ((address) displaced->clear_lock_bits ())) { entry->lock ()->set_displaced_header (NULL ); } else { CALL_VM (InterpreterRuntime::monitorenter (THREAD, entry), handle_exception); } } } UPDATE_PC_AND_TOS_AND_CONTINUE (1 , -1 ); } else { istate->set_msg (more_monitors); UPDATE_PC_AND_RETURN (0 ); } }
再回顾下对象头中mark word的格式:
JVM中的每个类也有一个类似mark word的prototype_header,用来标记该class的epoch和偏向开关等信息。上面的代码中lockee->klass()->prototype_header()
即获取class的prototype_header。
code 1
,从当前线程的栈中找到一个空闲的Lock Record
(即代码中的BasicObjectLock,下文都用Lock Record代指 ),判断Lock Record
是否空闲的依据是其obj字段 是否为null。注意这里是按内存地址从低往高找到最后一个可用的Lock Record
,换而言之,就是找到内存地址最高的可用Lock Record
。
code 2
,获取到Lock Record
后,首先要做的就是为其obj字段赋值。
code 3
,判断锁对象的mark word
是否是偏向模式,即低3位是否为101。
code 4
,这里有几步位运算的操作anticipated_bias_locking_value = (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) & ~((uintptr_t) markOopDesc::age_mask_in_place);
这个位运算可以分为3个部分。
第一部分((uintptr_t)lockee->klass()->prototype_header() | thread_ident)
将当前线程id和类的prototype_header相或,这样得到的值为(当前线程id + prototype_header中的(epoch + 分代年龄 + 偏向锁标志 + 锁标志位)),注意prototype_header的分代年龄那4个字节为0
第二部分 ^ (uintptr_t)mark
将上面计算得到的结果与锁对象的markOop进行异或,相等的位全部被置为0,只剩下不相等的位。
第三部分 & ~((uintptr_t) markOopDesc::age_mask_in_place)
markOopDesc::age_mask_in_place为…0001111000,取反后,变成了…1110000111,除了分代年龄那4位,其他位全为1;将取反后的结果再与上面的结果相与,将上面异或得到的结果中分代年龄给忽略掉。
code 5
,anticipated_bias_locking_value==0
代表偏向的线程是当前线程且mark word
的epoch等于class的epoch,这种情况下什么都不用做。
code 6
,(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0
代表class的prototype_header或对象的mark word
中偏向模式是关闭的,又因为能走到这已经通过了mark->has_bias_pattern()
判断,即对象的mark word
中偏向模式是开启的,那也就是说class的prototype_header不是偏向模式。
然后利用CAS指令Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark
撤销偏向锁,我们知道CAS会有几个参数,1是预期的原值,2是预期修改后的值 ,3是要修改的对象,与之对应,cmpxchg_ptr方法第一个参数是预期修改后的值,第2个参数是修改的对象,第3个参数是预期原值,方法返回实际原值,如果等于预期原值则说明修改成功。
code 7,如果epoch已过期,则需要重偏向,利用CAS指令将锁对象的mark word
替换为一个偏向当前线程且epoch为类的epoch的新的mark word
。
code 8,CAS将偏向线程改为当前线程,如果当前是匿名偏向则能修改成功,否则进入锁升级的逻辑。
code 9,这一步已经是轻量级锁的逻辑了。从上图的mark word
的格式可以看到,轻量级锁中mark word
存的是指向Lock Record
的指针。这里构造一个无锁状态的mark word
,然后存储到Lock Record
(Lock Record
的格式可以看第一篇文章)。设置mark word
是无锁状态的原因是:轻量级锁解锁时是将对象头的mark word
设置为Lock Record
中的Displaced Mark Word
,所以创建时设置为无锁状态,解锁时直接用CAS替换就好了。
code 10, 如果是锁重入,则将Lock Record
的Displaced Mark Word
设置为null,起到一个锁重入计数的作用。
以上是偏向锁加锁的流程(包括部分轻量级锁的加锁流程),如果当前锁已偏向其他线程||epoch值过期||偏向模式关闭||获取偏向锁的过程中存在并发冲突,都会进入到InterpreterRuntime::monitorenter
方法, 在该方法中会对偏向锁撤销和升级。
偏向锁的撤销 这里说的撤销是指在获取偏向锁的过程因为不满足条件导致要将锁对象改为非偏向锁状态;释放是指退出同步块时的过程,释放锁的逻辑会在下一小节阐述。请读者注意本文中撤销与释放的区别 。
如果获取偏向锁失败会进入到InterpreterRuntime::monitorenter 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 IRT_ENTRY_NO_ASYNC (void , InterpreterRuntime::monitorenter (JavaThread* thread, BasicObjectLock* elem)) ... Handle h_obj (thread, elem->obj()) ; assert (Universe::heap ()->is_in_reserved_or_null (h_obj ()), "must be NULL or an object" ); if (UseBiasedLocking) { ObjectSynchronizer::fast_enter (h_obj, elem->lock (), true , CHECK); } else { ObjectSynchronizer::slow_enter (h_obj, elem->lock (), CHECK); } ... IRT_END
可以看到如果开启了JVM偏向锁,那会进入到ObjectSynchronizer::fast_enter
方法中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void ObjectSynchronizer::fast_enter (Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) { if (UseBiasedLocking) { if (!SafepointSynchronize::is_at_safepoint ()) { BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias (obj, attempt_rebias, THREAD); if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) { return ; } } else { assert (!attempt_rebias, "can not rebias toward VM thread" ); BiasedLocking::revoke_at_safepoint (obj); } assert (!obj->mark ()->has_bias_pattern (), "biases should be revoked by now" ); } slow_enter (obj, lock, THREAD) ; }
如果是正常的Java线程,会走上面的逻辑进入到BiasedLocking::revoke_and_rebias
方法,如果是VM线程则会走到下面的BiasedLocking::revoke_at_safepoint
。我们主要看BiasedLocking::revoke_and_rebias
方法。这个方法的主要作用像它的方法名:撤销或者重偏向,第一个参数封装了锁对象和当前线程,第二个参数代表是否允许重偏向,这里是true。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 BiasedLocking::Condition BiasedLocking::revoke_and_rebias (Handle obj, bool attempt_rebias, TRAPS) { assert (!SafepointSynchronize::is_at_safepoint (), "must not be called while at safepoint" ); markOop mark = obj->mark (); if (mark->is_biased_anonymously () && !attempt_rebias) { markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype ()->set_age (mark->age ()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr (unbiased_prototype, obj->mark_addr (), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } else if (mark->has_bias_pattern ()) { Klass* k = obj->klass (); markOop prototype_header = k->prototype_header (); if (!prototype_header->has_bias_pattern ()) { markOop biased_value = mark; markOop res_mark = (markOop) Atomic::cmpxchg_ptr (prototype_header, obj->mark_addr (), mark); assert (!(*(obj->mark_addr ()))->has_bias_pattern (), "even if we raced, should still be revoked" ); return BIAS_REVOKED; } else if (prototype_header->bias_epoch () != mark->bias_epoch ()) { if (attempt_rebias) { assert (THREAD->is_Java_thread (), "" ); markOop biased_value = mark; markOop rebiased_prototype = markOopDesc::encode ((JavaThread*) THREAD, mark->age (), prototype_header->bias_epoch ()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr (rebiased_prototype, obj->mark_addr (), mark); if (res_mark == biased_value) { return BIAS_REVOKED_AND_REBIASED; } } else { markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype ()->set_age (mark->age ()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr (unbiased_prototype, obj->mark_addr (), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } } } HeuristicsResult heuristics = update_heuristics (obj (), attempt_rebias); if (heuristics == HR_NOT_BIASED) { return NOT_BIASED; } else if (heuristics == HR_SINGLE_REVOKE) { Klass *k = obj->klass (); markOop prototype_header = k->prototype_header (); if (mark->biased_locker () == THREAD && prototype_header->bias_epoch () == mark->bias_epoch ()) { ResourceMark rm; if (TraceBiasedLocking) { tty->print_cr ("Revoking bias by walking my own stack:" ); } BiasedLocking::Condition cond = revoke_bias (obj (), false , false , (JavaThread*) THREAD); ((JavaThread*) THREAD)->set_cached_monitor_info (NULL ); assert (cond == BIAS_REVOKED, "why not?" ); return cond; } else { VM_RevokeBias revoke (&obj, (JavaThread*) THREAD); VMThread::execute (&revoke); return revoke.status_code (); } } assert ((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?" ); VM_BulkRevokeBias bulk_revoke (&obj, (JavaThread*) THREAD, (heuristics == HR_BULK_REBIAS), attempt_rebias) ; VMThread::execute (&bulk_revoke); return bulk_revoke.status_code (); }
会走到该方法的逻辑有很多,我们只分析最常见的情况:假设锁已经偏向线程A,这时B线程尝试获得锁。
上面的code 1
,code 2
B线程都不会走到,最终会走到code 4
处,如果要撤销的锁偏向的是当前线程则直接调用revoke_bias
撤销偏向锁,否则会将该操作push到VM Thread中等到safepoint
的时候再执行。
关于VM Thread这里介绍下:在JVM中有个专门的VM Thread,该线程会源源不断的从VMOperationQueue中取出请求,比如GC请求。对于需要safepoint
的操作(VM_Operationevaluate_at_safepoint返回true)必须要等到所有的Java线程进入到safepoint
才开始执行。 关于safepoint
可以参考下这篇文章 。
接下来我们着重分析下revoke_bias
方法。第一个参数为锁对象,第2、3个参数为都为false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 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 ) { if (!allow_rebias) { obj->set_mark (unbiased_prototype); } ... return BiasedLocking::BIAS_REVOKED; } 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; } 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* mon_info = cached_monitor_info->at (i); if (mon_info->owner () == obj) { ... markOop mark = markOopDesc::encode ((BasicLock*) NULL ); highest_lock = mon_info->lock (); highest_lock->set_displaced_header (mark); } else { ... } } if (highest_lock != NULL ) { highest_lock->set_displaced_header (unbiased_prototype); obj->release_set_mark (markOopDesc::encode (highest_lock)); ... } else { ... if (allow_rebias) { obj->set_mark (biased_prototype); } else { obj->set_mark (unbiased_prototype); } } return BiasedLocking::BIAS_REVOKED; }
需要注意下,当调用锁对象的Object#hash
或System.identityHashCode()
方法会导致该对象的偏向锁或轻量级锁升级。这是因为在Java中一个对象的hashcode是在调用这两个方法时才生成的,如果是无锁状态则存放在mark word
中,如果是重量级锁则存放在对应的monitor中,而偏向锁是没有地方能存放该信息的,所以必须升级。具体可以看这篇文章 的hashcode()方法对偏向锁的影响
小节(注意:该文中对于偏向锁的加锁描述有些错误),另外我也向该文章作者请教过一些问题,他很热心的回答了我,在此感谢一下!
言归正传,revoke_bias
方法逻辑:
查看偏向的线程是否存活,如果已经不存活了,则直接撤销偏向锁。JVM维护了一个集合存放所有存活的线程,通过遍历该集合判断某个线程是否存活。
偏向的线程是否还在同步块中,如果不在了,则撤销偏向锁。我们回顾一下偏向锁的加锁流程:每次进入同步块(即执行monitorenter
)的时候都会以从高往低的顺序在栈中找到第一个可用的Lock Record
,将其obj字段指向锁对象。每次解锁(即执行monitorexit
)的时候都会将最低的一个相关Lock Record
移除掉。所以可以通过遍历线程栈中的Lock Record
来判断线程是否还在同步块中。
将偏向线程所有相关Lock Record
的Displaced Mark Word
设置为null,然后将最高位的Lock Record
的Displaced Mark Word
设置为无锁状态,最高位的Lock Record
也就是第一次获得锁时的Lock Record
(这里的第一次是指重入获取锁时的第一次),然后将对象头指向最高位的Lock Record
,这里不需要用CAS指令,因为是在safepoint
。 执行完后,就升级成了轻量级锁。原偏向线程的所有Lock Record都已经变成轻量级锁的状态。这里如果看不明白,请回顾上篇文章的轻量级锁加锁过程。
偏向锁的释放 偏向锁的释放入口在bytecodeInterpreter.cpp#1923
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 CASE (_monitorexit): { oop lockee = STACK_OBJECT (-1 ); CHECK_NULL (lockee); BasicObjectLock* limit = istate->monitor_base (); BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base (); while (most_recent != limit ) { if ((most_recent)->obj () == lockee) { BasicLock* lock = most_recent->lock (); markOop header = lock->displaced_header (); most_recent->set_obj (NULL ); if (!lockee->mark ()->has_bias_pattern ()) { bool call_vm = UseHeavyMonitors; if (header != NULL || call_vm) { if (call_vm || Atomic::cmpxchg_ptr (header, lockee->mark_addr (), lock) != lock) { most_recent->set_obj (lockee); CALL_VM (InterpreterRuntime::monitorexit (THREAD, most_recent), handle_exception); } } } UPDATE_PC_AND_TOS_AND_CONTINUE (1 , -1 ); } most_recent++; } CALL_VM (InterpreterRuntime::throw_illegal_monitor_state_exception (THREAD), handle_exception); ShouldNotReachHere (); }
上面的代码结合注释理解起来应该不难,偏向锁的释放很简单,只要将对应Lock Record
释放就好了,而轻量级锁则需要将Displaced Mark Word
替换到对象头的mark word中。如果CAS失败或者是重量级锁则进入到InterpreterRuntime::monitorexit
方法中。该方法会在轻量级与重量级锁的文章中讲解。
批量重偏向和批量撤销 批量重偏向和批量撤销的背景可以看上篇文章,相关实现在BiasedLocking::revoke_and_rebias
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 BiasedLocking::Condition BiasedLocking::revoke_and_rebias (Handle obj, bool attempt_rebias, TRAPS) { ... HeuristicsResult heuristics = update_heuristics (obj (), attempt_rebias); ... assert ((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?" ); VM_BulkRevokeBias bulk_revoke (&obj, (JavaThread*) THREAD, (heuristics == HR_BULK_REBIAS), attempt_rebias) ; VMThread::execute (&bulk_revoke); return bulk_revoke.status_code (); }
在每次撤销偏向锁的时候都通过update_heuristics
方法记录下来,以类为单位,当某个类的对象撤销偏向次数达到一定阈值的时候JVM就认为该类不适合偏向模式或者需要重新偏向另一个对象,update_heuristics
就会返回HR_BULK_REVOKE
或HR_BULK_REBIAS
。进行批量撤销或批量重偏向。
先看update_heuristics
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 static HeuristicsResult update_heuristics (oop o, bool allow_rebias) { markOop mark = o->mark (); if (!mark->has_bias_pattern ()) { return HR_NOT_BIASED; } Klass* k = o->klass (); jlong cur_time = os::javaTimeMillis (); jlong last_bulk_revocation_time = k->last_biased_lock_bulk_revocation_time (); int revocation_count = k->biased_lock_revocation_count (); if ((revocation_count >= BiasedLockingBulkRebiasThreshold) && (revocation_count < BiasedLockingBulkRevokeThreshold) && (last_bulk_revocation_time != 0 ) && (cur_time - last_bulk_revocation_time >= BiasedLockingDecayTime)) { k->set_biased_lock_revocation_count (0 ); revocation_count = 0 ; } if (revocation_count <= BiasedLockingBulkRevokeThreshold) { revocation_count = k->atomic_incr_biased_lock_revocation_count (); } if (revocation_count == BiasedLockingBulkRevokeThreshold) { return HR_BULK_REVOKE; } if (revocation_count == BiasedLockingBulkRebiasThreshold) { return HR_BULK_REBIAS; } return HR_SINGLE_REVOKE; }
当达到阈值的时候就会通过VM 线程在safepoint
调用bulk_revoke_or_rebias_at_safepoint
, 参数bulk_rebias
如果是true代表是批量重偏向否则为批量撤销。attempt_rebias_of_object
代表对操作的锁对象o
是否运行重偏向,这里是true
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 static BiasedLocking::Condition bulk_revoke_or_rebias_at_safepoint (oop o, bool bulk_rebias, bool attempt_rebias_of_object, JavaThread* requesting_thread) { ... jlong cur_time = os::javaTimeMillis (); o->klass ()->set_last_biased_lock_bulk_revocation_time (cur_time); Klass* k_o = o->klass (); Klass* klass = k_o; if (bulk_rebias) { if (klass->prototype_header ()->has_bias_pattern ()) { int prev_epoch = klass->prototype_header ()->bias_epoch (); klass->set_prototype_header (klass->prototype_header ()->incr_bias_epoch ()); int cur_epoch = klass->prototype_header ()->bias_epoch (); for (JavaThread* thr = Threads::first (); thr != NULL ; thr = thr->next ()) { GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info (thr); for (int i = 0 ; i < cached_monitor_info->length (); i++) { MonitorInfo* mon_info = cached_monitor_info->at (i); oop owner = mon_info->owner (); markOop mark = owner->mark (); if ((owner->klass () == k_o) && mark->has_bias_pattern ()) { assert (mark->bias_epoch () == prev_epoch || mark->bias_epoch () == cur_epoch, "error in bias epoch adjustment" ); owner->set_mark (mark->set_bias_epoch (cur_epoch)); } } } } revoke_bias (o, attempt_rebias_of_object && klass->prototype_header ()->has_bias_pattern (), true , requesting_thread); } else { ... klass->set_prototype_header (markOopDesc::prototype ()); for (JavaThread* thr = Threads::first (); thr != NULL ; thr = thr->next ()) { GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info (thr); for (int i = 0 ; i < cached_monitor_info->length (); i++) { MonitorInfo* mon_info = cached_monitor_info->at (i); oop owner = mon_info->owner (); markOop mark = owner->mark (); if ((owner->klass () == k_o) && mark->has_bias_pattern ()) { revoke_bias (owner, false , true , requesting_thread); } } } revoke_bias (o, false , true , requesting_thread); } ... BiasedLocking::Condition status_code = BiasedLocking::BIAS_REVOKED; if (attempt_rebias_of_object && o->mark ()->has_bias_pattern () && klass->prototype_header ()->has_bias_pattern ()) { markOop new_mark = markOopDesc::encode (requesting_thread, o->mark ()->age (), klass->prototype_header ()->bias_epoch ()); o->set_mark (new_mark); status_code = BiasedLocking::BIAS_REVOKED_AND_REBIASED; ... } ... return status_code; }
该方法分为两个逻辑:批量重偏向和批量撤销。
先看批量重偏向,分为两步:
code 1
将类中的撤销计数器自增1,之后当该类已存在的实例获得锁时,就会尝试重偏向,相关逻辑在偏向锁获取流程
小节中。
code 2
处理当前正在被使用的锁对象,通过遍历所有存活线程的栈,找到所有正在使用的偏向锁对象,然后更新它们的epoch值。也就是说不会重偏向正在使用的锁,否则会破坏锁的线程安全性。
批量撤销逻辑如下:
code 3
将类的偏向标记关闭,之后当该类已存在的实例获得锁时,就会升级为轻量级锁;该类新分配的对象的mark word
则是无锁模式。
code 4
处理当前正在被使用的锁对象,通过遍历所有存活线程的栈,找到所有正在使用的偏向锁对象,然后撤销偏向锁。