摘要:前言網(wǎng)上各路大神總結(jié)過(guò)各種關(guān)于內(nèi)部實(shí)現(xiàn),看別人的文章總覺(jué)得不過(guò)癮,所以有了這篇文章,嘗試再扒一次的底褲數(shù)據(jù)結(jié)構(gòu)在分析源代碼之前需要了解相關(guān)概念,比如等,參考網(wǎng)絡(luò)上各種解說(shuō)或者之前系列文章,這里重點(diǎn)介紹一下,,每個(gè)在內(nèi)部都有一個(gè)的對(duì)象與之對(duì)應(yīng)
前言
網(wǎng)上各路大神總結(jié)過(guò)各種關(guān)于 hotspot jvm synchronized 內(nèi)部實(shí)現(xiàn),看別人的文章總覺(jué)得不過(guò)癮,所以有了這篇文章,嘗試再扒一次 synchronized 的“底褲”
數(shù)據(jù)結(jié)構(gòu)在分析源代碼之前需要了解相關(guān)概念,比如 oop, oopDesc, markOop 等,參考網(wǎng)絡(luò)上各種解說(shuō)或者之前系列文章,這里重點(diǎn)介紹一下 markOop,ObjectWaiter,ObjectMonitor .etc
markOop每個(gè) Java Object 在 JVM 內(nèi)部都有一個(gè) native 的 C++ 對(duì)象 oop/oopDesc 與之對(duì)應(yīng),回顧一下 oopDesc 的類定義(內(nèi)存布局)
class oopDesc { private: volatile markOop _mark; }
_mark 被聲明在 oopDesc 類的頂部,所以這個(gè) _mark 可以認(rèn)為是一個(gè) 頭部(就像 TCP/IP 數(shù)據(jù)包頭部),我們知道"頭部"一般保存著一些重要的狀態(tài)和標(biāo)志信息,在 markOop.hpp 文件頭部有一大段注釋說(shuō)明 markOop 內(nèi)存布局
// 32 bits: // -------- // hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object) // JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object) // size:32 ------------------------------------------>| (CMS free block) // PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
這里只列出 32 位機(jī)器上 markOop 的內(nèi)存布局,同樣的 32 bit 在不同的 object(normal, biased)以及不同的 CMS 垃圾搜集狀態(tài)下有不同的解釋,這種緊湊的內(nèi)存復(fù)用技術(shù)在 C/C++ 系統(tǒng)編程中隨處可見(jiàn)
對(duì)于 normal object,32 bit 位分為 4 個(gè)字段,其中和 synchronized 相關(guān)的是 biased_lock 和 lock
hash,對(duì)象的 hash 值
age,對(duì)象的年齡,分代 GC 相關(guān)
biased_lock,偏向鎖標(biāo)志
lock,對(duì)象鎖標(biāo)志
占兩比特,用于描述 3 種狀態(tài) locked, unlocked, monitor
// [ptr | 00] locked ptr points to real header on stack // [header | 0 | 01] unlocked regular object header // [ptr | 10] monitor inflated lock (header is wapped out) // [ptr | 11] marked used by markSweep to mark an object // not valid at any other time
對(duì)于 biased boject,biased_lock 比特位被設(shè)置,如果對(duì)象被偏向鎖定,擁有該偏向鎖的線程指針被保存在 markOop 的高位
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread // [0 | epoch | age | 1 | 01] lock is anonymously biasedObjectWaiter
如果一個(gè)線程在等待 object monitor(對(duì)象監(jiān)視器),虛擬機(jī)會(huì)創(chuàng)建一個(gè) ObjectWaiter 對(duì)象,并通過(guò) _next 和 _prev 指針將 ObjectWaiter 掛載到 object monitor 中的等待隊(duì)列中
class ObjectWaiter : public StackObject { public: ObjectWaiter * volatile _next; ObjectWaiter * volatile _prev; Thread* _thread ... }ObjectMonitor
ObjectMonitor 類是對(duì) 對(duì)象監(jiān)視器 的封裝,由于比較重要(關(guān)鍵),objectMonitor.hpp 文件中對(duì)它進(jìn)行了大段注釋
// The ObjectMonitor class implements the heavyweight version of a // JavaMonitor. The lightweight BasicLock/stack lock version has been // inflated into an ObjectMonitor. This inflation is typically due to // contention or use of Object.wait().
從注釋可以看出 ObjectMonitor 是 JavaMonitor(對(duì)象鎖)的一個(gè)重量級(jí)實(shí)現(xiàn),而偏向鎖和 stack lock(?)是另一種輕量級(jí)實(shí)現(xiàn),當(dāng)調(diào)用 Object.wait() 方法時(shí),輕量級(jí) JavaMonitor 會(huì)膨脹(提升)成重量級(jí)實(shí)現(xiàn)
關(guān)鍵字段 _owner當(dāng)前擁有該 ObjectMonitor 的線程
_EntryList由 ObjectWaiter 組成的雙向鏈表,JVM 會(huì)從該鏈表中取出一個(gè) ObjectWaiter 并喚醒對(duì)應(yīng)的 JavaThread
_cxqJVM 為每個(gè)嘗試進(jìn)入 synchronized 代碼段的 JavaThread 創(chuàng)建一個(gè) ObjectWaiter 并添加到 _cxq 隊(duì)列中
_WaitSetJVM 為每個(gè)調(diào)用 Object.wait() 方法的線程創(chuàng)建一個(gè) ObjectWaiter 并添加到 _WaitSet 隊(duì)列中
synchronized 實(shí)現(xiàn)在進(jìn)入 synchronized 代碼塊或方法時(shí),javac 會(huì)插入一條 monitorenter 字節(jié)碼指令,退出時(shí)插入一條 monitorexit 指令,我們還是以 Zero 解釋器為例來(lái)看看 monitorenter/monitorexit 指令是如何實(shí)現(xiàn)的,關(guān)于 Zero 解釋器相關(guān)概念可以參考之前的文章
monitorenter在 bytecodeInterpreter.cpp 中能夠找到 monitorenter 對(duì)應(yīng)的 case,大概流程如下:
獲取方法隱含的 this 參數(shù),即 oop
獲取對(duì)象頭部 markOop(參考上文),判斷是否有偏向標(biāo)志(has_bias_pattern),如果沒(méi)有轉(zhuǎn)到 4
偏向鎖相關(guān)的處理邏輯
嘗試使用輕量級(jí)鎖,這里使用了 CAW(compare and swap,比較和交換)原語(yǔ)來(lái)保證線程對(duì) oop 中 markOop
字段的獨(dú)占寫入,成功寫入的線程立即返回(接著運(yùn)行),失敗的線程則調(diào)用 InterpreterRuntime::monitorenter
方法(重量級(jí)鎖)
至此可以看出加鎖的順序:偏向鎖 -> 輕量級(jí)鎖 -> 重量級(jí)鎖
CASE(_monitorenter): { oop lockee = STACK_OBJECT(-1); ... if (entry != NULL) { entry->set_obj(lockee); ... markOop mark = lockee->mark(); intptr_t hash = (intptr_t) markOopDesc::no_hash; if (mark->has_bias_pattern()) { // 嘗試使用偏向鎖... } // 嘗試使用輕量級(jí)鎖 // traditional lightweight locking 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) { // Is it simple recursive case? 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); } } } } else { istate->set_msg(more_monitors); UPDATE_PC_AND_RETURN(0); } }
我們先把偏向鎖相關(guān)的代碼放一遍,接著看 InterpreterRuntime::monitorenter 方法,為了使代碼更加清晰,我們忽略掉斷言和條件編譯,
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)) if (PrintBiasedLockingStatistics) { Atomic::inc(BiasedLocking::slow_path_entry_count_addr()); } Handle h_obj(thread, elem->obj()); 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
看來(lái) JVM 還是不死心,這里又有兩個(gè)分支 fast_enter 和 slow_enter,由于一路上我們都是挑著最慢的路徑走,這回也不例外,接著扒 slow_enter 方法
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) { markOop mark = obj->mark(); if (mark->is_neutral()) { lock->set_displaced_header(mark); if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) { TEVENT(slow_enter: release stacklock); return; } // Fall through to inflate() ... } else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { lock->set_displaced_header(NULL); return; } lock->set_displaced_header(markOopDesc::unused_mark()); ObjectSynchronizer::inflate(THREAD, obj(), inflate_cause_monitor_enter)->enter(THREAD); }
再次通過(guò) cmpxchg 嘗試輕量級(jí)鎖,否則調(diào)用 ObjectSynchronizer:: inflate 方法膨脹成重量級(jí)鎖(ObjectMonitor)并調(diào)用其 enter 方法
ObjectMonitor::enterObjectMonitor 對(duì)象有一個(gè) _owner 字段表明當(dāng)前哪個(gè)線程持有 ObjectMonitor,enter 方法首先通過(guò) cmpxchg 嘗試將 _owner 原子性設(shè)置成當(dāng)前線程,如果成功就直接返回,這樣可以避免進(jìn)行內(nèi)核線程的上下文切換
總結(jié)文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/66732.html
摘要:由此可見(jiàn),自旋鎖和各有優(yōu)劣,他們分別適用于競(jìng)爭(zhēng)不多和競(jìng)爭(zhēng)激烈的場(chǎng)景中。每一個(gè)試圖進(jìn)入同步代碼塊的線程都會(huì)被封裝成對(duì)象,它們或在對(duì)象的中,或在中,等待成為對(duì)象的成為的對(duì)象即獲取了監(jiān)視器鎖。 前言 系列文章目錄 前面兩篇文章我們介紹了synchronized同步代碼塊以及wait和notify機(jī)制,大致知道了這些關(guān)鍵字和方法是干什么的,以及怎么用。 但是,知其然,并不知其所以然。 例如...
摘要:前言方法是早期提供的一種基于的線程同步方法,本文先介紹相關(guān)的數(shù)據(jù)結(jié)構(gòu)類,然后從方法的內(nèi)部實(shí)現(xiàn)入手,簡(jiǎn)單分析相關(guān)的原理和實(shí)現(xiàn)類用于實(shí)現(xiàn)的定待和喚醒,不同平臺(tái)操作系統(tǒng)平臺(tái)對(duì)應(yīng)的定義在文件類的分配和釋放使用了對(duì)象緩存,靜態(tài)字段用于緩存當(dāng)前 前言 Object wait/notify 方法是早期 JVM 提供的一種基于 Object Monitor 的線程同步方法,本文先介紹相關(guān)的數(shù)據(jù)結(jié)構(gòu)(類...
摘要:前言本文從類的方法的內(nèi)部實(shí)現(xiàn)入手,分析多線程相關(guān)的數(shù)據(jù)結(jié)構(gòu)類和原理類方法類的方法用于啟動(dòng)線程,方法內(nèi)部調(diào)用了方法在源代碼中搜索,可以看到對(duì)應(yīng)函數(shù)在源代碼中搜索函數(shù)核心代碼計(jì)算線程堆棧大小創(chuàng)建對(duì)象初始化啟動(dòng)線程在創(chuàng)建時(shí)傳入了一個(gè)函數(shù)指針, 前言 本文從 Java Thread 類的 start 方法的內(nèi)部實(shí)現(xiàn)入手,分析 Hotspot JVM 多線程相關(guān)的數(shù)據(jù)結(jié)構(gòu)(類)和原理 Threa...
摘要:準(zhǔn)備工作假設(shè)源代碼目錄為編譯時(shí)啟用了解釋器參考編譯和調(diào)試調(diào)用棧先在函數(shù)參考虛擬機(jī)入口中設(shè)斷點(diǎn),然后在的方法中設(shè)置斷點(diǎn)通過(guò)宏獲取當(dāng)前,然后創(chuàng)建第個(gè)棧幀,然后進(jìn)入解釋執(zhí)行字節(jié)碼 準(zhǔn)備工作 假設(shè) openjdk 源代碼目錄為 jdk9dev 編譯 openjdk 時(shí)啟用了 zero 解釋器(參考 OpenJDK9 Hotspot Mac OSX 編譯和調(diào)試) 調(diào)用棧 先在 JavaMai...
摘要:占用率太高,還出各種奇怪問(wèn)題,轉(zhuǎn)投調(diào)試安裝下載源代碼漫長(zhǎng)等待,中間無(wú)數(shù)次中斷安裝安裝可選如果要使用解釋器,需要安裝設(shè)置調(diào)試級(jí)別,設(shè)成可以提供更多的調(diào)試信息設(shè)置路徑 Intellij CLion CPU 占用率太高,還出各種奇怪問(wèn)題,轉(zhuǎn)投 Xcode 調(diào)試 hotspot 安裝 hg # brew install hg 下載 open jdk 9 源代碼 # hg clone http...
閱讀 2485·2023-04-25 21:41
閱讀 1657·2021-09-22 15:17
閱讀 1928·2021-09-22 10:02
閱讀 2443·2021-09-10 11:21
閱讀 2585·2019-08-30 15:53
閱讀 1004·2019-08-30 15:44
閱讀 957·2019-08-30 13:46
閱讀 1146·2019-08-29 18:36