網上各路大神總結過各類關於 hotspot jvm synchronized 內部實現,看別人的文章總以爲不過癮,因此有了這篇文章,嘗試再扒一次 synchronized 的「底褲」java
在分析源代碼以前須要瞭解相關概念,好比 oop, oopDesc, markOop 等,參考網絡上各類解說或者以前系列文章,這裏重點介紹一下 markOop,ObjectWaiter,ObjectMonitor .etc編程
每一個 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++ 系統編程中隨處可見 app
對於 normal object,32 bit 位分爲 4 個字段,其中和 synchronized 相關的是 biased_lock 和 lockjvm
hash,對象的 hash 值oop
age,對象的年齡,分代 GC 相關佈局
biased_lock,偏向鎖標誌this
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 biased
若是一個線程在等待 object monitor(對象監視器),虛擬機會建立一個 ObjectWaiter 對象,並經過 _next 和 _prev 指針將 ObjectWaiter 掛載到 object monitor 中的等待隊列中
class ObjectWaiter : public StackObject { public: ObjectWaiter * volatile _next; ObjectWaiter * volatile _prev; Thread* _thread ... }
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 會膨脹(提高)成重量級實現
當前擁有該 ObjectMonitor 的線程
由 ObjectWaiter 組成的雙向鏈表,JVM 會從該鏈表中取出一個 ObjectWaiter 並喚醒對應的 JavaThread
JVM 爲每一個嘗試進入 synchronized 代碼段的 JavaThread 建立一個 ObjectWaiter 並添加到 _cxq 隊列中
JVM 爲每一個調用 Object.wait() 方法的線程建立一個 ObjectWaiter 並添加到 _WaitSet 隊列中
在進入 synchronized 代碼塊或方法時,javac 會插入一條 monitorenter 字節碼指令,退出時插入一條 monitorexit 指令,咱們仍是以 Zero 解釋器爲例來看看 monitorenter/monitorexit 指令是如何實現的,關於 Zero 解釋器相關概念能夠參考以前的文章
在 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 對象有一個 _owner 字段代表當前哪一個線程持有 ObjectMonitor,enter 方法首先經過 cmpxchg 嘗試將 _owner 原子性設置成當前線程,若是成功就直接返回,這樣能夠避免進行內核線程的上下文切換