本文為synchronized系列第二篇。主要內(nèi)容為分析偏向鎖的實現(xiàn)。
創(chuàng)新互聯(lián)網(wǎng)站建設(shè)由有經(jīng)驗的網(wǎng)站設(shè)計師、開發(fā)人員和項目經(jīng)理組成的專業(yè)建站團隊,負責(zé)網(wǎng)站視覺設(shè)計、用戶體驗優(yōu)化、交互設(shè)計和前端開發(fā)等方面的工作,以確保網(wǎng)站外觀精美、成都網(wǎng)站建設(shè)、成都網(wǎng)站制作易于使用并且具有良好的響應(yīng)性。偏向鎖的誕生背景和基本原理在上文中已經(jīng)講過了,強烈建議在有看過上篇文章的基礎(chǔ)下閱讀本文。
本文將分為幾塊內(nèi)容:
1.偏向鎖的入口
2.偏向鎖的獲取流程
3.偏向鎖的撤銷流程
4.偏向鎖的釋放流程
5.偏向鎖的批量重偏向和批量撤銷
本文分析的JVM版本是JVM8,具體版本號以及代碼可以在這里看到。
目前網(wǎng)上的很多文章,關(guān)于偏向鎖源碼入口都找錯地方了,導(dǎo)致我之前對于偏向鎖的很多邏輯一直想不通,走了很多彎路。
synchronized
分為synchronized
代碼塊和synchronized
方法,其底層獲取鎖的邏輯都是一樣的,本文講解的是synchronized
代碼塊的實現(xiàn)。上篇文章也說過,synchronized
代碼塊是由monitorenter
和monitorexit
兩個指令實現(xiàn)的。
關(guān)于HotSpot虛擬機中獲取鎖的入口,網(wǎng)上很多文章要么給出的方法入口為interpreterRuntime.cpp#monitorenter,要么給出的入口為bytecodeInterpreter.cpp#1816。包括占小狼的這篇文章關(guān)于鎖入口的位置說法也是有問題的(當(dāng)然文章還是很好的,在我剛開始研究synchronized
的時候,小狼哥的這篇文章給了我很多幫助)。
要找鎖的入口,肯定是要在源碼中找到對monitorenter
指令解析的地方。在HotSpot的中有兩處地方對monitorenter
指令進行解析:一個是在bytecodeInterpreter.cpp#1816?,另一個是在templateTable_x86_64.cpp#3667。
前者是JVM中的字節(jié)碼解釋器(bytecodeInterpreter
),用C++實現(xiàn)了每條JVM指令(如monitorenter
、invokevirtual
等),其優(yōu)點是實現(xiàn)相對簡單且容易理解,缺點是執(zhí)行慢。后者是模板解釋器(templateInterpreter
),其對每個指令都寫了一段對應(yīng)的匯編代碼,啟動時將每個指令與對應(yīng)匯編代碼入口綁定,可以說是效率做到了極致。模板解釋器的實現(xiàn)可以看這篇文章,在研究的過程中也請教過文章作者‘汪先生’一些問題,這里感謝一下。
在HotSpot中,只用到了模板解釋器,字節(jié)碼解釋器根本就沒用到,R大的讀書筆記中說的很清楚了,大家可以看看,這里不再贅述。
所以montorenter
的解析入口在模板解釋器中,其代碼位于templateTable_x86_64.cpp#3667。通過調(diào)用路徑:templateTable_x86_64#monitorenter
->interp_masm_x86_64#lock_object
進入到偏向鎖入口macroAssembler_x86#biased_locking_enter
,在這里大家可以看到會生成對應(yīng)的匯編代碼。需要注意的是,不是說每次解析monitorenter
指令都會調(diào)用biased_locking_enter
,而是只會在JVM啟動的時候調(diào)用該方法生成匯編代碼,之后對指令的解析是通過直接執(zhí)行匯編代碼。
其實bytecodeInterpreter
的邏輯和templateInterpreter
的邏輯是大同小異的,因為templateInterpreter
中都是匯編代碼,比較晦澀,所以看bytecodeInterpreter
的實現(xiàn)會便于理解一點。但這里有個坑,在jdk8u之前,bytecodeInterpreter
并沒有實現(xiàn)偏向鎖的邏輯。我之前看的JDK8-87ee5ee27509這個版本就沒有實現(xiàn)偏向鎖的邏輯,導(dǎo)致我看了很久都沒看懂。在這個commit中對bytecodeInterpreter
加入了偏向鎖的支持,我大致了看了下和templateInterpreter
對比除了棧結(jié)構(gòu)不同外,其他邏輯大致相同,所以下文就按bytecodeInterpreter中的代碼對偏向鎖邏輯進行講解。templateInterpreter
的匯編代碼講解可以看這篇文章,其實匯編源碼中都有英文注釋,了解了匯編幾個基本指令的作用再結(jié)合注釋理解起來也不是很難。
下面開始偏向鎖獲取流程分析,代碼在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的狀態(tài)是偏向模式 ????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:如果偏向模式關(guān)閉,則嘗試撤銷偏向鎖 ??????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)?{ ????????//?構(gòu)造一個偏向當(dāng)前線程的mark?word ????????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); ????????} ????????//?CAS替換對象頭的mark?word?? ????????if?(Atomic::cmpxchg_ptr((void*)new_header,?lockee->mark_addr(),?mark)?==?mark)?{ ??????????if?(PrintBiasedLockingStatistics) ????????????(*?BiasedLocking::rebiased_lock_entry_count_addr())++; ????????} ????????else?{ ??????????//?重偏向失敗,代表存在多線程競爭,則調(diào)用monitorenter方法進行鎖升級 ??????????CALL_VM(InterpreterRuntime::monitorenter(THREAD,?entry),?handle_exception); ????????} ????????success?=?true; ??????} ??????else?{ ?????????//?走到這里說明當(dāng)前要么偏向別的線程,要么是匿名偏向(即沒有偏向任何線程) ??????? //?code?8:下面構(gòu)建一個匿名偏向的mark?word,嘗試用CAS指令替換掉鎖對象的mark?word ????????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); ????????//?debugging?hint ????????DEBUG_ONLY(entry->lock()->set_displaced_header((markOop)?(uintptr_t)?0xdeaddead);) ????????if?(Atomic::cmpxchg_ptr((void*)new_header,?lockee->mark_addr(),?header)?==?header)?{ ???????????//?CAS修改成功 ??????????if?(PrintBiasedLockingStatistics) ????????????(*?BiasedLocking::anonymously_biased_lock_entry_count_addr())++; ????????} ????????else?{ ??????????//?如果修改失敗說明存在多線程競爭,所以進入monitorenter方法 ??????????CALL_VM(InterpreterRuntime::monitorenter(THREAD,?entry),?handle_exception); ????????} ????????success?=?true; ??????} ????} ????//?如果偏向線程不是當(dāng)前線程或沒有開啟偏向模式等原因都會導(dǎo)致success==false ????if?(!success)?{ ??????//?輕量級鎖的邏輯 ??????//code?9:?構(gòu)造一個無鎖狀態(tài)的Displaced?Mark?Word,并將Lock?Record的lock指向它 ??????markOop?displaced?=?lockee->mark()->set_unlocked(); ??????entry->lock()->set_displaced_header(displaced); ??????//如果指定了-XX:+UseHeavyMonitors,則call_vm=true,代表禁用偏向鎖和輕量級鎖 ??????bool?call_vm?=?UseHeavyMonitors; ??????//?利用CAS將對象頭的mark?word替換為指向Lock?Record的指針 ??????if?(call_vm?||?Atomic::cmpxchg_ptr(entry,?lockee->mark_addr(),?displaced)?!=?displaced)?{ ????????//?判斷是不是鎖重入 ????????if?(!call_vm?&&?THREAD->is_lock_owned((address)?displaced->clear_lock_bits()))?{ //code?10:?如果是鎖重入,則直接將Displaced?Mark?Word設(shè)置為null ??????????entry->lock()->set_displaced_header(NULL); ????????}?else?{ ??????????CALL_VM(InterpreterRuntime::monitorenter(THREAD,?entry),?handle_exception); ????????} ??????} ????} ????UPDATE_PC_AND_TOS_AND_CONTINUE(1,?-1); ??}?else?{ ????//?lock?record不夠,重新執(zhí)行 ????istate->set_msg(more_monitors); ????UPDATE_PC_AND_RETURN(0);?//?Re-execute ??} }
再回顧下對象頭中mark word的格式:
JVM中的每個類也有一個類似mark word的prototype_header,用來標記該class的epoch和偏向開關(guān)等信息。上面的代碼中lockee->klass()->prototype_header()
即獲取class的prototype_header。
code 1
,從當(dāng)前線程的棧中找到一個空閑的Lock Record
(即代碼中的BasicObjectLock,下文都用Lock Record代指),判斷Lock Record
是否空閑的依據(jù)是其obj字段 是否為null。注意這里是按內(nèi)存地址從低往高找到最后一個可用的Lock Record
,換而言之,就是找到內(nèi)存地址最高的可用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)
將當(dāng)前線程id和類的prototype_header相或,這樣得到的值為(當(dāng)前線程id + prototype_header中的(epoch + 分代年齡 + 偏向鎖標志 + 鎖標志位)),注意prototype_header的分代年齡那4個字節(jié)為0
第二部分?^ (uintptr_t)mark
將上面計算得到的結(jié)果與鎖對象的markOop進行異或,相等的位全部被置為0,只剩下不相等的位。
第三部分?& ~((uintptr_t) markOopDesc::age_mask_in_place)
markOopDesc::age_mask_in_place為…0001111000,取反后,變成了…1110000111,除了分代年齡那4位,其他位全為1;將取反后的結(jié)果再與上面的結(jié)果相與,將上面異或得到的結(jié)果中分代年齡給忽略掉。
code 5
,anticipated_bias_locking_value==0
代表偏向的線程是當(dāng)前線程且mark word
的epoch等于class的epoch,這種情況下什么都不用做。
code 6
,(anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0
代表class的prototype_header或?qū)ο蟮?code>mark word中偏向模式是關(guān)閉的,又因為能走到這已經(jīng)通過了mark->has_bias_pattern()
判斷,即對象的mark word
中偏向模式是開啟的,那也就是說class的prototype_header不是偏向模式。
然后利用CAS指令Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark
撤銷偏向鎖,我們知道CAS會有幾個參數(shù),1是預(yù)期的原值,2是預(yù)期修改后的值 ,3是要修改的對象,與之對應(yīng),cmpxchg_ptr方法第一個參數(shù)是預(yù)期修改后的值,第2個參數(shù)是修改的對象,第3個參數(shù)是預(yù)期原值,方法返回實際原值,如果等于預(yù)期原值則說明修改成功。
code 7,如果epoch已過期,則需要重偏向,利用CAS指令將鎖對象的mark word
替換為一個偏向當(dāng)前線程且epoch為類的epoch的新的mark word
。
code 8,CAS將偏向線程改為當(dāng)前線程,如果當(dāng)前是匿名偏向則能修改成功,否則進入鎖升級的邏輯。
code 9,這一步已經(jīng)是輕量級鎖的邏輯了。從上圖的mark word
的格式可以看到,輕量級鎖中mark word
存的是指向L
ock Record
的指針。這里構(gòu)造一個無鎖狀態(tài)的mark word
,然后存儲到Lock Record
(Lock Record
的格式可以看第一篇文章)。設(shè)置mark word
是無鎖狀態(tài)的原因是:輕量級鎖解鎖時是將對象頭的mark word
設(shè)置為Lock Record
中的Displaced Mark Word
,所以創(chuàng)建時設(shè)置為無鎖狀態(tài),解鎖時直接用CAS替換就好了。
code 10, 如果是鎖重入,則將Lock Record
的Displaced Mark Word
設(shè)置為null,起到一個鎖重入計數(shù)的作用。
以上是偏向鎖加鎖的流程(包括部分輕量級鎖的加鎖流程),如果當(dāng)前鎖已偏向其他線程||epoch值過期||偏向模式關(guān)閉||獲取偏向鎖的過程中存在并發(fā)沖突,都會進入到InterpreterRuntime::monitorenter
方法, 在該方法中會對偏向鎖撤銷和升級。
這里說的撤銷是指在獲取偏向鎖的過程因為不滿足條件導(dǎo)致要將鎖對象改為非偏向鎖狀態(tài);釋放是指退出同步塊時的過程,釋放鎖的邏輯會在下一小節(jié)闡述。請讀者注意本文中撤銷與釋放的區(qū)別。
如果獲取偏向鎖失敗會進入到InterpreterRuntime::monitorenter方法
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)?{ ????//?Retry?fast?entry?if?bias?is?revoked?to?avoid?unnecessary?inflation ????ObjectSynchronizer::fast_enter(h_obj,?elem->lock(),?true,?CHECK); ??}?else?{ ????ObjectSynchronizer::slow_enter(h_obj,?elem->lock(),?CHECK); ??} ??... IRT_END
可以看到如果開啟了JVM偏向鎖,那會進入到ObjectSynchronizer::fast_enter
方法中。
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
方法。這個方法的主要作用像它的方法名:撤銷或者重偏向,第一個參數(shù)封裝了鎖對象和當(dāng)前線程,第二個參數(shù)代表是否允許重偏向,這里是true。
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)?{ ?????//如果是匿名偏向且attempt_rebias==false會走到這里,如鎖對象的hashcode方法被調(diào)用會出現(xiàn)這種情況,需要撤銷偏向鎖。 ????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(); ????//code?1:?如果對應(yīng)class關(guān)閉了偏向模式 ????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; ????//code2:?如果epoch過期 ????}?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; ????????} ??????} ????} ??} ??//code?3:批量重偏向與批量撤銷的邏輯 ??HeuristicsResult?heuristics?=?update_heuristics(obj(),?attempt_rebias); ??if?(heuristics?==?HR_NOT_BIASED)?{ ????return?NOT_BIASED; ??}?else?if?(heuristics?==?HR_SINGLE_REVOKE)?{ ????//code?4:撤銷單個線程 ????Klass?*k?=?obj->klass(); ????markOop?prototype_header?=?k->prototype_header(); ????if?(mark->biased_locker()?==?THREAD?&& ????????prototype_header->bias_epoch()?==?mark->bias_epoch())?{ ??????//?走到這里說明需要撤銷的是偏向當(dāng)前線程的鎖,當(dāng)調(diào)用Object#hashcode方法時會走到這一步 ??????//?因為只要遍歷當(dāng)前線程的棧就好了,所以不需要等到safepoint再撤銷。 ??????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線程中的safepoint調(diào)用revoke_bias方法 ??????VM_RevokeBias?revoke(&obj,?(JavaThread*)?THREAD); ??????VMThread::execute(&revoke); ??????return?revoke.status_code(); ????} ??} ??assert((heuristics?==?HR_BULK_REVOKE)?|| ?????????(heuristics?==?HR_BULK_REBIAS),?"?"); ???//code5:批量撤銷、批量重偏向的邏輯 ??VM_BulkRevokeBias?bulk_revoke(&obj,?(JavaThread*)?THREAD, ????????????????????????????????(heuristics?==?HR_BULK_REBIAS), ????????????????????????????????attempt_rebias); ??VMThread::execute(&bulk_revoke); ??return?bulk_revoke.status_code(); }
會走到該方法的邏輯有很多,我們只分析最常見的情況:假設(shè)鎖已經(jīng)偏向線程A,這時B線程嘗試獲得鎖。
上面的code 1
,code 2
B線程都不會走到,最終會走到code 4
處,如果要撤銷的鎖偏向的是當(dāng)前線程則直接調(diào)用revoke_bias
撤銷偏向鎖,否則會將該操作push到VM Thread中等到safepoint
的時候再執(zhí)行。
關(guān)于VM Thread這里介紹下:在JVM中有個專門的VM Thread,該線程會源源不斷的從VMOperationQueue中取出請求,比如GC請求。對于需要safepoint
的操作(VM_Operationevaluate_at_safepoint返回true)必須要等到所有的Java線程進入到safepoint
才開始執(zhí)行。
接下來我們著重分析下revoke_bias
方法。第一個參數(shù)為鎖對象,第2、3個參數(shù)為都為false
static?BiasedLocking::Condition?revoke_bias(oop?obj,?bool?allow_rebias,?bool?is_bulk,?JavaThread*?requesting_thread)?{ ??markOop?mark?=?obj->mark(); ??//?如果沒有開啟偏向模式,則直接返回NOT_BIASED ??if?(!mark->has_bias_pattern())?{ ????... ????return?BiasedLocking::NOT_BIASED; ??} ??uint?age?=?mark->age(); ??//?構(gòu)建兩個mark?word,一個是匿名偏向模式(101),一個是無鎖模式(001) ??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)?{ ?????//?匿名偏向。當(dāng)調(diào)用鎖對象的hashcode()方法可能會導(dǎo)致走到這個邏輯 ?????//?如果不允許重偏向,則將對象的mark?word設(shè)置為無鎖模式 ????if?(!allow_rebias)?{ ??????obj->set_mark(unbiased_prototype); ????} ????... ????return?BiasedLocking::BIAS_REVOKED; ??} ??//?code?1:判斷偏向線程是否還存活 ??bool?thread_is_alive?=?false; ??//?如果當(dāng)前線程就是偏向線程? ??if?(requesting_thread?==?biased_thread)?{ ????thread_is_alive?=?true; ??}?else?{ ?????//?遍歷當(dāng)前jvm的所有線程,如果能找到,則說明偏向的線程還存活 ????for?(JavaThread*?cur_thread?=?Threads::first();?cur_thread?!=?NULL;?cur_thread?=?cur_thread->next())?{ ??????if?(cur_thread?==?biased_thread)?{ ????????thread_is_alive?=?true; ????????break; ??????} ????} ??} ??//?如果偏向的線程已經(jīng)不存活了 ??if?(!thread_is_alive)?{ ????//?允許重偏向則將對象mark?word設(shè)置為匿名偏向狀態(tài),否則設(shè)置為無鎖狀態(tài) ????if?(allow_rebias)?{ ??????obj->set_mark(biased_prototype); ????}?else?{ ??????obj->set_mark(unbiased_prototype); ????} ????... ????return?BiasedLocking::BIAS_REVOKED; ??} ??//?線程還存活則遍歷線程棧中所有的Lock?Record ??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); ????//?如果能找到對應(yīng)的Lock?Record說明偏向的線程還在執(zhí)行同步代碼塊中的代碼 ????if?(mon_info->owner()?==?obj)?{ ??????... ??????//?需要升級為輕量級鎖,直接修改偏向線程棧中的Lock?Record。為了處理鎖重入的case,在這里將Lock?Record的Displaced?Mark?Word設(shè)置為null,第一個Lock?Record會在下面的代碼中再處理 ??????markOop?mark?=?markOopDesc::encode((BasicLock*)?NULL); ??????highest_lock?=?mon_info->lock(); ??????highest_lock->set_displaced_header(mark); ????}?else?{ ??????... ????} ??} ??if?(highest_lock?!=?NULL)?{ ????//?修改第一個Lock?Record為無鎖狀態(tài),然后將obj的mark?word設(shè)置為指向該Lock?Record的指針 ????highest_lock->set_displaced_header(unbiased_prototype); ????obj->release_set_mark(markOopDesc::encode(highest_lock)); ????... ??}?else?{ ????//?走到這里說明偏向線程已經(jīng)不在同步塊中了 ????... ????if?(allow_rebias)?{ ???????//設(shè)置為匿名偏向狀態(tài) ??????obj->set_mark(biased_prototype); ????}?else?{ ??????//?將mark?word設(shè)置為無鎖狀態(tài) ??????obj->set_mark(unbiased_prototype); ????} ??} ??return?BiasedLocking::BIAS_REVOKED; }
需要注意下,當(dāng)調(diào)用鎖對象的Object#hash
或System.identityHashCode()
方法會導(dǎo)致該對象的偏向鎖或輕量級鎖升級。這是因為在Java中一個對象的hashcode是在調(diào)用這兩個方法時才生成的,如果是無鎖狀態(tài)則存放在mark word
中,如果是重量級鎖則存放在對應(yīng)的monitor中,而偏向鎖是沒有地方能存放該信息的,所以必須升級。
言歸正傳,revoke_bias
方法邏輯:
查看偏向的線程是否存活,如果已經(jīng)不存活了,則直接撤銷偏向鎖。JVM維護了一個集合存放所有存活的線程,通過遍歷該集合判斷某個線程是否存活。
偏向的線程是否還在同步塊中,如果不在了,則撤銷偏向鎖。我們回顧一下偏向鎖的加鎖流程:每次進入同步塊(即執(zhí)行monitorenter
)的時候都會以從高往低的順序在棧中找到第一個可用的Lock Record
,將其obj字段指向鎖對象。每次解鎖(即執(zhí)行monitorexit
)的時候都會將最低的一個相關(guān)Lock Record
移除掉。所以可以通過遍歷線程棧中的Lock Record
來判斷線程是否還在同步塊中。
將偏向線程所有相關(guān)Lock Record
的Displaced Mark Word
設(shè)置為null,然后將最高位的Lock Record
的Displaced Mark Word
設(shè)置為無鎖狀態(tài),最高位的Lock Record
也就是第一次獲得鎖時的Lock Record
(這里的第一次是指重入獲取鎖時的第一次),然后將對象頭指向最高位的Lock Record
,這里不需要用CAS指令,因為是在safepoint
。 執(zhí)行完后,就升級成了輕量級鎖。原偏向線程的所有Lock Record都已經(jīng)變成輕量級鎖的狀態(tài)。這里如果看不明白,請回顧上篇文章的輕量級鎖加鎖過程。
偏向鎖的釋放入口在bytecodeInterpreter.cpp#1923
CASE(_monitorexit):?{ ??oop?lockee?=?STACK_OBJECT(-1); ??CHECK_NULL(lockee); ??//?derefing's?lockee?ought?to?provoke?implicit?null?check ??//?find?our?monitor?slot ??BasicObjectLock*?limit?=?istate->monitor_base(); ??BasicObjectLock*?most_recent?=?(BasicObjectLock*)?istate->stack_base(); ??//?從低往高遍歷棧的Lock?Record ??while?(most_recent?!=?limit?)?{ ????//?如果Lock?Record關(guān)聯(lián)的是該鎖對象 ????if?((most_recent)->obj()?==?lockee)?{ ??????BasicLock*?lock?=?most_recent->lock(); ??????markOop?header?=?lock->displaced_header(); ??????//?釋放Lock?Record ??????most_recent->set_obj(NULL); ??????//?如果是偏向模式,僅僅釋放Lock?Record就好了。否則要走輕量級鎖or重量級鎖的釋放流程 ??????if?(!lockee->mark()->has_bias_pattern())?{ ????????bool?call_vm?=?UseHeavyMonitors; ????????//?header!=NULL說明不是重入,則需要將Displaced?Mark?Word?CAS到對象頭的Mark?Word ????????if?(header?!=?NULL?||?call_vm)?{ ??????????if?(call_vm?||?Atomic::cmpxchg_ptr(header,?lockee->mark_addr(),?lock)?!=?lock)?{ ????????????//?CAS失敗或者是重量級鎖則會走到這里,先將obj還原,然后調(diào)用monitorexit方法 ????????????most_recent->set_obj(lockee); ????????????CALL_VM(InterpreterRuntime::monitorexit(THREAD,?most_recent),?handle_exception); ??????????} ????????} ??????} ??????//執(zhí)行下一條命令 ??????UPDATE_PC_AND_TOS_AND_CONTINUE(1,?-1); ????} ????//處理下一條Lock?Record ????most_recent++; ??} ??//?Need?to?throw?illegal?monitor?state?exception ??CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD),?handle_exception); ??ShouldNotReachHere(); }
上面的代碼結(jié)合注釋理解起來應(yīng)該不難,偏向鎖的釋放很簡單,只要將對應(yīng)Lock Record
釋放就好了,而輕量級鎖則需要將Displaced Mark Word
替換到對象頭的mark word中。如果CAS失敗或者是重量級鎖則進入到InterpreterRuntime::monitorexit
方法中。該方法會在輕量級與重量級鎖的文章中講解。
批量重偏向和批量撤銷的背景可以看上篇文章,相關(guān)實現(xiàn)在BiasedLocking::revoke_and_rebias
中:
BiasedLocking::Condition?BiasedLocking::revoke_and_rebias(Handle?obj,?bool?attempt_rebias,?TRAPS)?{ ??... ??//code?1:重偏向的邏輯 ??HeuristicsResult?heuristics?=?update_heuristics(obj(),?attempt_rebias); ??//?非重偏向的邏輯 ??... ?????? ??assert((heuristics?==?HR_BULK_REVOKE)?|| ?????????(heuristics?==?HR_BULK_REBIAS),?"?"); ???//code?2:批量撤銷、批量重偏向的邏輯 ??VM_BulkRevokeBias?bulk_revoke(&obj,?(JavaThread*)?THREAD, ????????????????????????????????(heuristics?==?HR_BULK_REBIAS), ????????????????????????????????attempt_rebias); ??VMThread::execute(&bulk_revoke); ??return?bulk_revoke.status_code(); }
在每次撤銷偏向鎖的時候都通過update_heuristics
方法記錄下來,以類為單位,當(dāng)某個類的對象撤銷偏向次數(shù)達到一定閾值的時候JVM就認為該類不適合偏向模式或者需要重新偏向另一個對象,update_heuristics
就會返回HR_BULK_REVOKE
或HR_BULK_REBIAS
。進行批量撤銷或批量重偏向。
先看update_heuristics
方法。
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(); ??//?當(dāng)前時間 ??jlong?cur_time?=?os::javaTimeMillis(); ??//?該類上一次批量撤銷的時間 ??jlong?last_bulk_revocation_time?=?k->last_biased_lock_bulk_revocation_time(); ??//?該類偏向鎖撤銷的次數(shù) ??int?revocation_count?=?k->biased_lock_revocation_count(); ??//?BiasedLockingBulkRebiasThreshold是重偏向閾值(默認20),BiasedLockingBulkRevokeThreshold是批量撤銷閾值(默認40),BiasedLockingDecayTime是開啟一次新的批量重偏向距離上次批量重偏向的后的延遲時間,默認25000。也就是開啟批量重偏向后,經(jīng)過了一段較長的時間(>=BiasedLockingDecayTime),撤銷計數(shù)器才超過閾值,那我們會重置計數(shù)器。 ??if?((revocation_count?>=?BiasedLockingBulkRebiasThreshold)?&& ??????(revocation_count?<??BiasedLockingBulkRevokeThreshold)?&& ??????(last_bulk_revocation_time?!=?0)?&& ??????(cur_time?-?last_bulk_revocation_time?>=?BiasedLockingDecayTime))?{ ????//?This?is?the?first?revocation?we've?seen?in?a?while?of?an ????//?object?of?this?type?since?the?last?time?we?performed?a?bulk ????//?rebiasing?operation.?The?application?is?allocating?objects?in ????//?bulk?which?are?biased?toward?a?thread?and?then?handing?them ????//?off?to?another?thread.?We?can?cope?with?this?allocation ????//?pattern?via?the?bulk?rebiasing?mechanism?so?we?reset?the ????//?klass's?revocation?count?rather?than?allow?it?to?increase ????//?monotonically.?If?we?see?the?need?to?perform?another?bulk ????//?rebias?operation?later,?we?will,?and?if?subsequently?we?see ????//?many?more?revocation?operations?in?a?short?period?of?time?we ????//?will?completely?disable?biasing?for?this?type. ????k->set_biased_lock_revocation_count(0); ????revocation_count?=?0; ??} ??//?自增撤銷計數(shù)器 ??if?(revocation_count?<=?BiasedLockingBulkRevokeThreshold)?{ ????revocation_count?=?k->atomic_incr_biased_lock_revocation_count(); ??} ??//?如果達到批量撤銷閾值則返回HR_BULK_REVOKE ??if?(revocation_count?==?BiasedLockingBulkRevokeThreshold)?{ ????return?HR_BULK_REVOKE; ??} ??//?如果達到批量重偏向閾值則返回HR_BULK_REBIAS ??if?(revocation_count?==?BiasedLockingBulkRebiasThreshold)?{ ????return?HR_BULK_REBIAS; ??} ??//?沒有達到閾值則撤銷單個對象的鎖 ??return?HR_SINGLE_REVOKE; }
當(dāng)達到閾值的時候就會通過VM 線程在safepoint
調(diào)用bulk_revoke_or_rebias_at_safepoint
, 參數(shù)bulk_rebias
如果是true代表是批量重偏向否則為批量撤銷。attempt_rebias_of_object
代表對操作的鎖對象o
是否運行重偏向,這里是true
。
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())?{ ??????//?自增前類中的的epoch ??????int?prev_epoch?=?klass->prototype_header()->bias_epoch(); ??????//?code?1:類中的epoch自增 ??????klass->set_prototype_header(klass->prototype_header()->incr_bias_epoch()); ??????int?cur_epoch?=?klass->prototype_header()->bias_epoch(); ??????//?code?2:遍歷所有線程的棧,更新類型為該klass的所有鎖實例的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())?{ ????????????//?We?might?have?encountered?this?object?already?in?the?case?of?recursive?locking ????????????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)); ??????????} ????????} ??????} ????} ????//?接下來對當(dāng)前鎖對象進行重偏向 ????revoke_bias(o,?attempt_rebias_of_object?&&?klass->prototype_header()->has_bias_pattern(),?true,?requesting_thread); ??}?else?{ ????... ????//?code?3:批量撤銷的邏輯,將類中的偏向標記關(guān)閉,markOopDesc::prototype()返回的是一個關(guān)閉偏向模式的prototype ????klass->set_prototype_header(markOopDesc::prototype()); ????//?code?4:遍歷所有線程的棧,撤銷該類所有鎖的偏向 ????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); ????????} ??????} ????} ????//?撤銷當(dāng)前鎖對象的偏向模式 ????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())?{ ????//?構(gòu)造一個偏向請求線程的mark?word ????markOop?new_mark?=?markOopDesc::encode(requesting_thread,?o->mark()->age(), ???????????????????????????????????????????klass->prototype_header()->bias_epoch()); ????//?更新當(dāng)前鎖對象的mark?word ????o->set_mark(new_mark); ????status_code?=?BiasedLocking::BIAS_REVOKED_AND_REBIASED; ????... ??} ??... ??return?status_code; }
該方法分為兩個邏輯:批量重偏向和批量撤銷。
先看批量重偏向,分為兩步:
code 1
將類中的撤銷計數(shù)器自增1,之后當(dāng)該類已存在的實例獲得鎖時,就會嘗試重偏向,相關(guān)邏輯在偏向鎖獲取流程
小節(jié)中。
code 2
處理當(dāng)前正在被使用的鎖對象,通過遍歷所有存活線程的棧,找到所有正在使用的偏向鎖對象,然后更新它們的epoch值。也就是說不會重偏向正在使用的鎖,否則會破壞鎖的線程安全性。
批量撤銷邏輯如下:
code 3
將類的偏向標記關(guān)閉,之后當(dāng)該類已存在的實例獲得鎖時,就會升級為輕量級鎖;該類新分配的對象的mark word
則是無鎖模式。
code 4
處理當(dāng)前正在被使用的鎖對象,通過遍歷所有存活線程的棧,找到所有正在使用的偏向鎖對象,然后撤銷偏向鎖。
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國云服務(wù)器,動態(tài)BGP最優(yōu)骨干路由自動選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機房獨有T級流量清洗系統(tǒng)配攻擊溯源,準確進行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動現(xiàn)已開啟,新人活動云服務(wù)器買多久送多久。
名稱欄目:面試題深入解析:Synchronized底層實現(xiàn)-創(chuàng)新互聯(lián)
轉(zhuǎn)載源于:http://muchs.cn/article34/iogse.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供電子商務(wù)、做網(wǎng)站、響應(yīng)式網(wǎng)站、App設(shè)計、搜索引擎優(yōu)化、網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容