摘要:前言網上各路大神總結過各種關于內部實現,看別人的文章總覺得不過癮,所以有了這篇文章,嘗試再扒一次的底褲數據結構在分析源代碼之前需要了解相關概念,比如等,參考網絡上各種解說或者之前系列文章,這里重點介紹一下,,每個在內部都有一個的對象與之對應
前言
網上各路大神總結過各種關于 hotspot jvm synchronized 內部實現,看別人的文章總覺得不過癮,所以有了這篇文章,嘗試再扒一次 synchronized 的“底褲”
數據結構在分析源代碼之前需要了解相關概念,比如 oop, oopDesc, markOop 等,參考網絡上各種解說或者之前系列文章,這里重點介紹一下 markOop,ObjectWaiter,ObjectMonitor .etc
markOop每個 Java Object 在 JVM 內部都有一個 native 的 C++ 對象 oop/oopDesc 與之對應,回顧一下 oopDesc 的類定義(內存布局)
class oopDesc { private: volatile markOop _mark; }
_mark 被聲明在 oopDesc 類的頂部,所以這個 _mark 可以認為是一個 頭部(就像 TCP/IP 數據包頭部),我們知道"頭部"一般保存著一些重要的狀態和標志信息,在 markOop.hpp 文件頭部有一大段注釋說明 markOop 內存布局
// 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 位機器上 markOop 的內存布局,同樣的 32 bit 在不同的 object(normal, biased)以及不同的 CMS 垃圾搜集狀態下有不同的解釋,這種緊湊的內存復用技術在 C/C++ 系統編程中隨處可見
對于 normal object,32 bit 位分為 4 個字段,其中和 synchronized 相關的是 biased_lock 和 lock
hash,對象的 hash 值
age,對象的年齡,分代 GC 相關
biased_lock,偏向鎖標志
lock,對象鎖標志
占兩比特,用于描述 3 種狀態 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
對于 biased boject,biased_lock 比特位被設置,如果對象被偏向鎖定,擁有該偏向鎖的線程指針被保存在 markOop 的高位
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread // [0 | epoch | age | 1 | 01] lock is anonymously biasedObjectWaiter
如果一個線程在等待 object monitor(對象監視器),虛擬機會創建一個 ObjectWaiter 對象,并通過 _next 和 _prev 指針將 ObjectWaiter 掛載到 object monitor 中的等待隊列中
class ObjectWaiter : public StackObject { public: ObjectWaiter * volatile _next; ObjectWaiter * volatile _prev; Thread* _thread ... }ObjectMonitor
ObjectMonitor 類是對 對象監視器 的封裝,由于比較重要(關鍵),objectMonitor.hpp 文件中對它進行了大段注釋
// 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(對象鎖)的一個重量級實現,而偏向鎖和 stack lock(?)是另一種輕量級實現,當調用 Object.wait() 方法時,輕量級 JavaMonitor 會膨脹(提升)成重量級實現
關鍵字段 _owner當前擁有該 ObjectMonitor 的線程
_EntryList由 ObjectWaiter 組成的雙向鏈表,JVM 會從該鏈表中取出一個 ObjectWaiter 并喚醒對應的 JavaThread
_cxqJVM 為每個嘗試進入 synchronized 代碼段的 JavaThread 創建一個 ObjectWaiter 并添加到 _cxq 隊列中
_WaitSetJVM 為每個調用 Object.wait() 方法的線程創建一個 ObjectWaiter 并添加到 _WaitSet 隊列中
synchronized 實現在進入 synchronized 代碼塊或方法時,javac 會插入一條 monitorenter 字節碼指令,退出時插入一條 monitorexit 指令,我們還是以 Zero 解釋器為例來看看 monitorenter/monitorexit 指令是如何實現的,關于 Zero 解釋器相關概念可以參考之前的文章
monitorenter在 bytecodeInterpreter.cpp 中能夠找到 monitorenter 對應的 case,大概流程如下:
獲取方法隱含的 this 參數,即 oop
獲取對象頭部 markOop(參考上文),判斷是否有偏向標志(has_bias_pattern),如果沒有轉到 4
偏向鎖相關的處理邏輯
嘗試使用輕量級鎖,這里使用了 CAW(compare and swap,比較和交換)原語來保證線程對 oop 中 markOop
字段的獨占寫入,成功寫入的線程立即返回(接著運行),失敗的線程則調用 InterpreterRuntime::monitorenter
方法(重量級鎖)
至此可以看出加鎖的順序:偏向鎖 -> 輕量級鎖 -> 重量級鎖
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()) { // 嘗試使用偏向鎖... } // 嘗試使用輕量級鎖 // 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); } }
我們先把偏向鎖相關的代碼放一遍,接著看 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
看來 JVM 還是不死心,這里又有兩個分支 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); }
再次通過 cmpxchg 嘗試輕量級鎖,否則調用 ObjectSynchronizer:: inflate 方法膨脹成重量級鎖(ObjectMonitor)并調用其 enter 方法
ObjectMonitor::enterObjectMonitor 對象有一個 _owner 字段表明當前哪個線程持有 ObjectMonitor,enter 方法首先通過 cmpxchg 嘗試將 _owner 原子性設置成當前線程,如果成功就直接返回,這樣可以避免進行內核線程的上下文切換
總結文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66732.html
摘要:由此可見,自旋鎖和各有優劣,他們分別適用于競爭不多和競爭激烈的場景中。每一個試圖進入同步代碼塊的線程都會被封裝成對象,它們或在對象的中,或在中,等待成為對象的成為的對象即獲取了監視器鎖。 前言 系列文章目錄 前面兩篇文章我們介紹了synchronized同步代碼塊以及wait和notify機制,大致知道了這些關鍵字和方法是干什么的,以及怎么用。 但是,知其然,并不知其所以然。 例如...
摘要:前言方法是早期提供的一種基于的線程同步方法,本文先介紹相關的數據結構類,然后從方法的內部實現入手,簡單分析相關的原理和實現類用于實現的定待和喚醒,不同平臺操作系統平臺對應的定義在文件類的分配和釋放使用了對象緩存,靜態字段用于緩存當前 前言 Object wait/notify 方法是早期 JVM 提供的一種基于 Object Monitor 的線程同步方法,本文先介紹相關的數據結構(類...
摘要:前言本文從類的方法的內部實現入手,分析多線程相關的數據結構類和原理類方法類的方法用于啟動線程,方法內部調用了方法在源代碼中搜索,可以看到對應函數在源代碼中搜索函數核心代碼計算線程堆棧大小創建對象初始化啟動線程在創建時傳入了一個函數指針, 前言 本文從 Java Thread 類的 start 方法的內部實現入手,分析 Hotspot JVM 多線程相關的數據結構(類)和原理 Threa...
摘要:準備工作假設源代碼目錄為編譯時啟用了解釋器參考編譯和調試調用棧先在函數參考虛擬機入口中設斷點,然后在的方法中設置斷點通過宏獲取當前,然后創建第個棧幀,然后進入解釋執行字節碼 準備工作 假設 openjdk 源代碼目錄為 jdk9dev 編譯 openjdk 時啟用了 zero 解釋器(參考 OpenJDK9 Hotspot Mac OSX 編譯和調試) 調用棧 先在 JavaMai...
摘要:占用率太高,還出各種奇怪問題,轉投調試安裝下載源代碼漫長等待,中間無數次中斷安裝安裝可選如果要使用解釋器,需要安裝設置調試級別,設成可以提供更多的調試信息設置路徑 Intellij CLion CPU 占用率太高,還出各種奇怪問題,轉投 Xcode 調試 hotspot 安裝 hg # brew install hg 下載 open jdk 9 源代碼 # hg clone http...
閱讀 2470·2023-04-25 21:41
閱讀 1647·2021-09-22 15:17
閱讀 1921·2021-09-22 10:02
閱讀 2433·2021-09-10 11:21
閱讀 2569·2019-08-30 15:53
閱讀 996·2019-08-30 15:44
閱讀 946·2019-08-30 13:46
閱讀 1125·2019-08-29 18:36