本文為synchronized系列第二篇。主要內(nèi)容為分析偏向鎖的實(shí)現(xiàn)。
10多年的通許網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。成都全網(wǎng)營銷的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整通許建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“通許網(wǎng)站設(shè)計(jì)”,“通許網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
偏向鎖的誕生背景和基本原理在上文中已經(jīng)講過了,強(qiáng)烈建議在有看過上篇文章的基礎(chǔ)下閱讀本文。
?
本文將分為幾塊內(nèi)容:
1.偏向鎖的入口
2.偏向鎖的獲取流程
3.偏向鎖的撤銷流程
4.偏向鎖的釋放流程
5.偏向鎖的批量重偏向和批量撤銷
本文分析的JVM版本是JVM8,具體版本號(hào)以及代碼可以在這里看到。
?
目前網(wǎng)上的很多文章,關(guān)于偏向鎖源碼入口都找錯(cuò)地方了,導(dǎo)致我之前對(duì)于偏向鎖的很多邏輯一直想不通,走了很多彎路。
synchronized
分為synchronized
代碼塊和synchronized
方法,其底層獲取鎖的邏輯都是一樣的,本文講解的是synchronized
代碼塊的實(shí)現(xiàn)。上篇文章也說過,synchronized
代碼塊是由monitorenter
和monitorexit
兩個(gè)指令實(shí)現(xiàn)的。
關(guān)于HotSpot虛擬機(jī)中獲取鎖的入口,網(wǎng)上很多文章要么給出的方法入口為interpreterRuntime.cpp#monitorenter,要么給出的入口為bytecodeInterpreter.cpp#1816。包括占小狼的這篇文章關(guān)于鎖入口的位置說法也是有問題的(當(dāng)然文章還是很好的,在我剛開始研究synchronized
的時(shí)候,小狼哥的這篇文章給了我很多幫助)。
要找鎖的入口,肯定是要在源碼中找到對(duì)monitorenter
指令解析的地方。在HotSpot的中有兩處地方對(duì)monitorenter
指令進(jìn)行解析:一個(gè)是在bytecodeInterpreter.cpp#1816?,另一個(gè)是在templateTable_x86_64.cpp#3667。
前者是JVM中的字節(jié)碼解釋器(bytecodeInterpreter
),用C++實(shí)現(xiàn)了每條JVM指令(如monitorenter
、invokevirtual
等),其優(yōu)點(diǎn)是實(shí)現(xiàn)相對(duì)簡(jiǎn)單且容易理解,缺點(diǎn)是執(zhí)行慢。后者是模板解釋器(templateInterpreter
),其對(duì)每個(gè)指令都寫了一段對(duì)應(yīng)的匯編代碼,啟動(dòng)時(shí)將每個(gè)指令與對(duì)應(yīng)匯編代碼入口綁定,可以說是效率做到了極致。模板解釋器的實(shí)現(xiàn)可以看這篇文章,在研究的過程中也請(qǐng)教過文章作者‘汪先生’一些問題,這里感謝一下。
在HotSpot中,只用到了模板解釋器,字節(jié)碼解釋器根本就沒用到,R大的讀書筆記中說的很清楚了,大家可以看看,這里不再贅述。
所以montorenter
的解析入口在模板解釋器中,其代碼位于templateTable_x86_64.cpp#3667。通過調(diào)用路徑:templateTable_x86_64#monitorenter
->interp_masm_x86_64#lock_object
進(jìn)入到偏向鎖入口macroAssembler_x86#biased_locking_enter
,在這里大家可以看到會(huì)生成對(duì)應(yīng)的匯編代碼。需要注意的是,不是說每次解析monitorenter
指令都會(huì)調(diào)用biased_locking_enter
,而是只會(huì)在JVM啟動(dòng)的時(shí)候調(diào)用該方法生成匯編代碼,之后對(duì)指令的解析是通過直接執(zhí)行匯編代碼。
其實(shí)bytecodeInterpreter
的邏輯和templateInterpreter
的邏輯是大同小異的,因?yàn)?code>templateInterpreter中都是匯編代碼,比較晦澀,所以看bytecodeInterpreter
的實(shí)現(xiàn)會(huì)便于理解一點(diǎn)。但這里有個(gè)坑,在jdk8u之前,bytecodeInterpreter
并沒有實(shí)現(xiàn)偏向鎖的邏輯。我之前看的JDK8-87ee5ee27509這個(gè)版本就沒有實(shí)現(xiàn)偏向鎖的邏輯,導(dǎo)致我看了很久都沒看懂。在這個(gè)commit中對(duì)bytecodeInterpreter
加入了偏向鎖的支持,我大致了看了下和templateInterpreter
對(duì)比除了棧結(jié)構(gòu)不同外,其他邏輯大致相同,所以下文就按bytecodeInterpreter中的代碼對(duì)偏向鎖邏輯進(jìn)行講解。templateInterpreter
的匯編代碼講解可以看這篇文章,其實(shí)匯編源碼中都有英文注釋,了解了匯編幾個(gè)基本指令的作用再結(jié)合注釋理解起來也不是很難。
下面開始偏向鎖獲取流程分析,代碼在bytecodeInterpreter.cpp#1816。注意本文代碼都有所刪減。
CASE(_monitorenter):?{ ??//?lockee?就是鎖對(duì)象 ??oop?lockee?=?STACK_OBJECT(-1); ??//?derefing's?lockee?ought?to?provoke?implicit?null?check ??CHECK_NULL(lockee); ??//?code?1:找到一個(gè)空閑的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指針指向鎖對(duì)象 ????entry->set_obj(lockee); ????int?success?=?false; ????uintptr_t?epoch_mask_in_place?=?(uintptr_t)markOopDesc::epoch_mask_in_place; //?markoop即對(duì)象頭的mark?word ????markOop?mark?=?lockee->mark(); ????intptr_t?hash?=?(intptr_t)?markOopDesc::no_hash; ????//?code?3:如果鎖對(duì)象的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)造一個(gè)偏向當(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替換對(duì)象頭的mark?word?? ????????if?(Atomic::cmpxchg_ptr((void*)new_header,?lockee->mark_addr(),?mark)?==?mark)?{ ??????????if?(PrintBiasedLockingStatistics) ????????????(*?BiasedLocking::rebiased_lock_entry_count_addr())++; ????????} ????????else?{ ??????????//?重偏向失敗,代表存在多線程競(jìng)爭(zhēng),則調(diào)用monitorenter方法進(jìn)行鎖升級(jí) ??????????CALL_VM(InterpreterRuntime::monitorenter(THREAD,?entry),?handle_exception); ????????} ????????success?=?true; ??????} ??????else?{ ?????????//?走到這里說明當(dāng)前要么偏向別的線程,要么是匿名偏向(即沒有偏向任何線程) ??????? //?code?8:下面構(gòu)建一個(gè)匿名偏向的mark?word,嘗試用CAS指令替換掉鎖對(duì)象的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?{ ??????????//?如果修改失敗說明存在多線程競(jìng)爭(zhēng),所以進(jìn)入monitorenter方法 ??????????CALL_VM(InterpreterRuntime::monitorenter(THREAD,?entry),?handle_exception); ????????} ????????success?=?true; ??????} ????} ????//?如果偏向線程不是當(dāng)前線程或沒有開啟偏向模式等原因都會(huì)導(dǎo)致success==false ????if?(!success)?{ ??????//?輕量級(jí)鎖的邏輯 ??????//code?9:?構(gòu)造一個(gè)無鎖狀態(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,代表禁用偏向鎖和輕量級(jí)鎖 ??????bool?call_vm?=?UseHeavyMonitors; ??????//?利用CAS將對(duì)象頭的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 ??} }
再回顧下對(duì)象頭中mark word的格式:
JVM中的每個(gè)類也有一個(gè)類似mark word的prototype_header,用來標(biāo)記該class的epoch和偏向開關(guān)等信息。上面的代碼中lockee->klass()->prototype_header()
即獲取class的prototype_header。
code 1
,從當(dāng)前線程的棧中找到一個(gè)空閑的Lock Record
(即代碼中的BasicObjectLock,下文都用Lock Record代指),判斷Lock Record
是否空閑的依據(jù)是其obj字段 是否為null。注意這里是按內(nèi)存地址從低往高找到最后一個(gè)可用的Lock Record
,換而言之,就是找到內(nèi)存地址最高的可用Lock Record
。
?
code 2
,獲取到Lock Record
后,首先要做的就是為其obj字段賦值。
?
code 3
,判斷鎖對(duì)象的mark word
是否是偏向模式,即低3位是否為101。
?
code 4
,這里有幾步位運(yùn)算的操作?anticipated_bias_locking_value = (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) & ?~((uintptr_t) markOopDesc::age_mask_in_place);
?這個(gè)位運(yùn)算可以分為3個(gè)部分。
?
第一部分((uintptr_t)lockee->klass()->prototype_header() | thread_ident)
?將當(dāng)前線程id和類的prototype_header相或,這樣得到的值為(當(dāng)前線程id + prototype_header中的(epoch + 分代年齡 + 偏向鎖標(biāo)志 + 鎖標(biāo)志位)),注意prototype_header的分代年齡那4個(gè)字節(jié)為0
?
第二部分?^ (uintptr_t)mark
?將上面計(jì)算得到的結(jié)果與鎖對(duì)象的markOop進(jìn)行異或,相等的位全部被置為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)閉的,又因?yàn)槟茏叩竭@已經(jīng)通過了mark->has_bias_pattern()
判斷,即對(duì)象的mark word
中偏向模式是開啟的,那也就是說class的prototype_header不是偏向模式。
?
然后利用CAS指令Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark
撤銷偏向鎖,我們知道CAS會(huì)有幾個(gè)參數(shù),1是預(yù)期的原值,2是預(yù)期修改后的值 ,3是要修改的對(duì)象,與之對(duì)應(yīng),cmpxchg_ptr方法第一個(gè)參數(shù)是預(yù)期修改后的值,第2個(gè)參數(shù)是修改的對(duì)象,第3個(gè)參數(shù)是預(yù)期原值,方法返回實(shí)際原值,如果等于預(yù)期原值則說明修改成功。
?
code 7,如果epoch已過期,則需要重偏向,利用CAS指令將鎖對(duì)象的mark word
替換為一個(gè)偏向當(dāng)前線程且epoch為類的epoch的新的mark word
。
?
code 8,CAS將偏向線程改為當(dāng)前線程,如果當(dāng)前是匿名偏向則能修改成功,否則進(jìn)入鎖升級(jí)的邏輯。
?
code 9,這一步已經(jīng)是輕量級(jí)鎖的邏輯了。從上圖的mark word
的格式可以看到,輕量級(jí)鎖中mark word
存的是指向L
ock Record
的指針。這里構(gòu)造一個(gè)無鎖狀態(tài)的mark word
,然后存儲(chǔ)到Lock Record
(Lock Record
的格式可以看第一篇文章)。設(shè)置mark word
是無鎖狀態(tài)的原因是:輕量級(jí)鎖解鎖時(shí)是將對(duì)象頭的mark word
設(shè)置為Lock Record
中的Displaced Mark Word
,所以創(chuàng)建時(shí)設(shè)置為無鎖狀態(tài),解鎖時(shí)直接用CAS替換就好了。
?
code 10, 如果是鎖重入,則將Lock Record
的Displaced Mark Word
設(shè)置為null,起到一個(gè)鎖重入計(jì)數(shù)的作用。
?
以上是偏向鎖加鎖的流程(包括部分輕量級(jí)鎖的加鎖流程),如果當(dāng)前鎖已偏向其他線程||epoch值過期||偏向模式關(guān)閉||獲取偏向鎖的過程中存在并發(fā)沖突,都會(huì)進(jìn)入到InterpreterRuntime::monitorenter
方法, 在該方法中會(huì)對(duì)偏向鎖撤銷和升級(jí)。
?
這里說的撤銷是指在獲取偏向鎖的過程因?yàn)椴粷M足條件導(dǎo)致要將鎖對(duì)象改為非偏向鎖狀態(tài);釋放是指退出同步塊時(shí)的過程,釋放鎖的邏輯會(huì)在下一小節(jié)闡述。請(qǐng)讀者注意本文中撤銷與釋放的區(qū)別。
如果獲取偏向鎖失敗會(huì)進(jìn)入到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偏向鎖,那會(huì)進(jìn)入到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線程,會(huì)走上面的邏輯進(jìn)入到BiasedLocking::revoke_and_rebias
方法,如果是VM線程則會(huì)走到下面的BiasedLocking::revoke_at_safepoint
。我們主要看BiasedLocking::revoke_and_rebias
方法。這個(gè)方法的主要作用像它的方法名:撤銷或者重偏向,第一個(gè)參數(shù)封裝了鎖對(duì)象和當(dāng)前線程,第二個(gè)參數(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會(huì)走到這里,如鎖對(duì)象的hashcode方法被調(diào)用會(huì)出現(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())?{ ????//?鎖對(duì)象開啟了偏向模式會(huì)走到這里 ????Klass*?k?=?obj->klass(); ????markOop?prototype_header?=?k->prototype_header(); ????//code?1:?如果對(duì)應(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:撤銷單個(gè)線程 ????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方法時(shí)會(huì)走到這一步 ??????//?因?yàn)橹灰闅v當(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?{ ??????//?下面代碼最終會(huì)在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(); }
會(huì)走到該方法的邏輯有很多,我們只分析最常見的情況:假設(shè)鎖已經(jīng)偏向線程A,這時(shí)B線程嘗試獲得鎖。
上面的code 1
,code 2
B線程都不會(huì)走到,最終會(huì)走到code 4
處,如果要撤銷的鎖偏向的是當(dāng)前線程則直接調(diào)用revoke_bias
撤銷偏向鎖,否則會(huì)將該操作push到VM Thread中等到safepoint
的時(shí)候再執(zhí)行。
關(guān)于VM Thread這里介紹下:在JVM中有個(gè)專門的VM Thread,該線程會(huì)源源不斷的從VMOperationQueue中取出請(qǐng)求,比如GC請(qǐng)求。對(duì)于需要safepoint
的操作(VM_Operationevaluate_at_safepoint返回true)必須要等到所有的Java線程進(jìn)入到safepoint
才開始執(zhí)行。
接下來我們著重分析下revoke_bias
方法。第一個(gè)參數(shù)為鎖對(duì)象,第2、3個(gè)參數(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)建兩個(gè)mark?word,一個(gè)是匿名偏向模式(101),一個(gè)是無鎖模式(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)用鎖對(duì)象的hashcode()方法可能會(huì)導(dǎo)致走到這個(gè)邏輯 ?????//?如果不允許重偏向,則將對(duì)象的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)?{ ????//?允許重偏向則將對(duì)象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*?cached_monitor_info?=?get_or_compute_monitor_info(biased_thread); ??BasicLock*?highest_lock?=?NULL; ??for?(int?i?=?0;?i?length();?i++)?{ ????MonitorInfo*?mon_info?=?cached_monitor_info->at(i); ????//?如果能找到對(duì)應(yīng)的Lock?Record說明偏向的線程還在執(zhí)行同步代碼塊中的代碼 ????if?(mon_info->owner()?==?obj)?{ ??????... ??????//?需要升級(jí)為輕量級(jí)鎖,直接修改偏向線程棧中的Lock?Record。為了處理鎖重入的case,在這里將Lock?Record的Displaced?Mark?Word設(shè)置為null,第一個(gè)Lock?Record會(huì)在下面的代碼中再處理 ??????markOop?mark?=?markOopDesc::encode((BasicLock*)?NULL); ??????highest_lock?=?mon_info->lock(); ??????highest_lock->set_displaced_header(mark); ????}?else?{ ??????... ????} ??} ??if?(highest_lock?!=?NULL)?{ ????//?修改第一個(gè)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)用鎖對(duì)象的Object#hash
或System.identityHashCode()
方法會(huì)導(dǎo)致該對(duì)象的偏向鎖或輕量級(jí)鎖升級(jí)。這是因?yàn)樵贘ava中一個(gè)對(duì)象的hashcode是在調(diào)用這兩個(gè)方法時(shí)才生成的,如果是無鎖狀態(tài)則存放在mark word
中,如果是重量級(jí)鎖則存放在對(duì)應(yīng)的monitor中,而偏向鎖是沒有地方能存放該信息的,所以必須升級(jí)。
言歸正傳,revoke_bias
方法邏輯:
查看偏向的線程是否存活,如果已經(jīng)不存活了,則直接撤銷偏向鎖。JVM維護(hù)了一個(gè)集合存放所有存活的線程,通過遍歷該集合判斷某個(gè)線程是否存活。
偏向的線程是否還在同步塊中,如果不在了,則撤銷偏向鎖。我們回顧一下偏向鎖的加鎖流程:每次進(jìn)入同步塊(即執(zhí)行monitorenter
)的時(shí)候都會(huì)以從高往低的順序在棧中找到第一個(gè)可用的Lock Record
,將其obj字段指向鎖對(duì)象。每次解鎖(即執(zhí)行monitorexit
)的時(shí)候都會(huì)將最低的一個(gè)相關(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
也就是第一次獲得鎖時(shí)的Lock Record
(這里的第一次是指重入獲取鎖時(shí)的第一次),然后將對(duì)象頭指向最高位的Lock Record
,這里不需要用CAS指令,因?yàn)槭窃?code>safepoint。 執(zhí)行完后,就升級(jí)成了輕量級(jí)鎖。原偏向線程的所有Lock Record都已經(jīng)變成輕量級(jí)鎖的狀態(tài)。這里如果看不明白,請(qǐng)回顧上篇文章的輕量級(jí)鎖加鎖過程。
偏向鎖的釋放入口在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)的是該鎖對(duì)象 ????if?((most_recent)->obj()?==?lockee)?{ ??????BasicLock*?lock?=?most_recent->lock(); ??????markOop?header?=?lock->displaced_header(); ??????//?釋放Lock?Record ??????most_recent->set_obj(NULL); ??????//?如果是偏向模式,僅僅釋放Lock?Record就好了。否則要走輕量級(jí)鎖or重量級(jí)鎖的釋放流程 ??????if?(!lockee->mark()->has_bias_pattern())?{ ????????bool?call_vm?=?UseHeavyMonitors; ????????//?header!=NULL說明不是重入,則需要將Displaced?Mark?Word?CAS到對(duì)象頭的Mark?Word ????????if?(header?!=?NULL?||?call_vm)?{ ??????????if?(call_vm?||?Atomic::cmpxchg_ptr(header,?lockee->mark_addr(),?lock)?!=?lock)?{ ????????????//?CAS失敗或者是重量級(jí)鎖則會(huì)走到這里,先將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)該不難,偏向鎖的釋放很簡(jiǎn)單,只要將對(duì)應(yīng)Lock Record
釋放就好了,而輕量級(jí)鎖則需要將Displaced Mark Word
替換到對(duì)象頭的mark word中。如果CAS失敗或者是重量級(jí)鎖則進(jìn)入到InterpreterRuntime::monitorexit
方法中。該方法會(huì)在輕量級(jí)與重量級(jí)鎖的文章中講解。
批量重偏向和批量撤銷的背景可以看上篇文章,相關(guān)實(shí)現(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(); }
在每次撤銷偏向鎖的時(shí)候都通過update_heuristics
方法記錄下來,以類為單位,當(dāng)某個(gè)類的對(duì)象撤銷偏向次數(shù)達(dá)到一定閾值的時(shí)候JVM就認(rèn)為該類不適合偏向模式或者需要重新偏向另一個(gè)對(duì)象,update_heuristics
就會(huì)返回HR_BULK_REVOKE
或HR_BULK_REBIAS
。進(jìn)行批量撤銷或批量重偏向。
先看update_heuristics
方法。
static?HeuristicsResult?update_heuristics(oop?o,?bool?allow_rebias)?{ ??markOop?mark?=?o->mark(); ??//如果不是偏向模式直接返回 ??if?(!mark->has_bias_pattern())?{ ????return?HR_NOT_BIASED; ??} ? ??//?鎖對(duì)象的類 ??Klass*?k?=?o->klass(); ??//?當(dāng)前時(shí)間 ??jlong?cur_time?=?os::javaTimeMillis(); ??//?該類上一次批量撤銷的時(shí)間 ??jlong?last_bulk_revocation_time?=?k->last_biased_lock_bulk_revocation_time(); ??//?該類偏向鎖撤銷的次數(shù) ??int?revocation_count?=?k->biased_lock_revocation_count(); ??//?BiasedLockingBulkRebiasThreshold是重偏向閾值(默認(rèn)20),BiasedLockingBulkRevokeThreshold是批量撤銷閾值(默認(rèn)40),BiasedLockingDecayTime是開啟一次新的批量重偏向距離上次批量重偏向的后的延遲時(shí)間,默認(rèn)25000。也就是開啟批量重偏向后,經(jīng)過了一段較長的時(shí)間(>=BiasedLockingDecayTime),撤銷計(jì)數(shù)器才超過閾值,那我們會(huì)重置計(jì)數(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; ??} ??//?自增撤銷計(jì)數(shù)器 ??if?(revocation_count?<=?BiasedLockingBulkRevokeThreshold)?{ ????revocation_count?=?k->atomic_incr_biased_lock_revocation_count(); ??} ??//?如果達(dá)到批量撤銷閾值則返回HR_BULK_REVOKE ??if?(revocation_count?==?BiasedLockingBulkRevokeThreshold)?{ ????return?HR_BULK_REVOKE; ??} ??//?如果達(dá)到批量重偏向閾值則返回HR_BULK_REBIAS ??if?(revocation_count?==?BiasedLockingBulkRebiasThreshold)?{ ????return?HR_BULK_REBIAS; ??} ??//?沒有達(dá)到閾值則撤銷單個(gè)對(duì)象的鎖 ??return?HR_SINGLE_REVOKE; }
當(dāng)達(dá)到閾值的時(shí)候就會(huì)通過VM 線程在safepoint
調(diào)用bulk_revoke_or_rebias_at_safepoint
, 參數(shù)bulk_rebias
如果是true代表是批量重偏向否則為批量撤銷。attempt_rebias_of_object
代表對(duì)操作的鎖對(duì)象o
是否運(yùn)行重偏向,這里是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的所有鎖實(shí)例的epoch ??????for?(JavaThread*?thr?=?Threads::first();?thr?!=?NULL;?thr?=?thr->next())?{ ????????GrowableArray*?cached_monitor_info?=?get_or_compute_monitor_info(thr); ????????for?(int?i?=?0;?i?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)); ??????????} ????????} ??????} ????} ????//?接下來對(duì)當(dāng)前鎖對(duì)象進(jìn)行重偏向 ????revoke_bias(o,?attempt_rebias_of_object?&&?klass->prototype_header()->has_bias_pattern(),?true,?requesting_thread); ??}?else?{ ????... ????//?code?3:批量撤銷的邏輯,將類中的偏向標(biāo)記關(guān)閉,markOopDesc::prototype()返回的是一個(gè)關(guān)閉偏向模式的prototype ????klass->set_prototype_header(markOopDesc::prototype()); ????//?code?4:遍歷所有線程的棧,撤銷該類所有鎖的偏向 ????for?(JavaThread*?thr?=?Threads::first();?thr?!=?NULL;?thr?=?thr->next())?{ ??????GrowableArray *?cached_monitor_info?=?get_or_compute_monitor_info(thr); ??????for?(int?i?=?0;?i?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)前鎖對(duì)象的偏向模式 ????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)造一個(gè)偏向請(qǐng)求線程的mark?word ????markOop?new_mark?=?markOopDesc::encode(requesting_thread,?o->mark()->age(), ???????????????????????????????????????????klass->prototype_header()->bias_epoch()); ????//?更新當(dāng)前鎖對(duì)象的mark?word ????o->set_mark(new_mark); ????status_code?=?BiasedLocking::BIAS_REVOKED_AND_REBIASED; ????... ??} ??... ??return?status_code; }
該方法分為兩個(gè)邏輯:批量重偏向和批量撤銷。
先看批量重偏向,分為兩步:
code 1
?將類中的撤銷計(jì)數(shù)器自增1,之后當(dāng)該類已存在的實(shí)例獲得鎖時(shí),就會(huì)嘗試重偏向,相關(guān)邏輯在偏向鎖獲取流程
小節(jié)中。
code 2
?處理當(dāng)前正在被使用的鎖對(duì)象,通過遍歷所有存活線程的棧,找到所有正在使用的偏向鎖對(duì)象,然后更新它們的epoch值。也就是說不會(huì)重偏向正在使用的鎖,否則會(huì)破壞鎖的線程安全性。
批量撤銷邏輯如下:
code 3
將類的偏向標(biāo)記關(guān)閉,之后當(dāng)該類已存在的實(shí)例獲得鎖時(shí),就會(huì)升級(jí)為輕量級(jí)鎖;該類新分配的對(duì)象的mark word
則是無鎖模式。
code 4
處理當(dāng)前正在被使用的鎖對(duì)象,通過遍歷所有存活線程的棧,找到所有正在使用的偏向鎖對(duì)象,然后撤銷偏向鎖。
?