轉自:https://www.aimoon.site/blog/2018/05/21/biased-locking/
比較復雜,簡略見另一篇:https://www.cnblogs.com/twoheads/p/10148598.html
JVM中的java對象頭
注意:在沒有特殊說明的情況下,都是32 bits為例。
上一小節主要介紹了java中synchronized
關鍵字的使用方法,而在這一小節中將介紹一下synchronized
在JVM中的實現基礎——java對象頭中的Mark Word
。
表1 Java對象頭的長度
內容 | 說明 | 備注 |
---|---|---|
Mark Word | 存儲對象的Mark Word信息 | - |
Class Metadata Address | 存儲指向對象存儲類型的指針 | - |
Array Length | 數組的長度 | 只有數組對象有該屬性 |
synchronized
使用的鎖是存放在Java對象頭中的Mark Word中,OpenJDK中的markOop.hpp 頭文件詳細介紹了Mark Word的內容,下面將分析32 bits的JVM中的Mark Word的構成。
表2 32位JVM的Mark Word存儲結構
鎖狀態 | 23 bits | 2 bits | 4 bits | 1 bit | 2 bits |
---|---|---|---|---|---|
輕量級鎖 | 指向棧中鎖記錄的指針 | 00 | |||
無鎖狀態 | hash code | 分代年齡 | 0 | 01 | |
偏向鎖 | Thread ID | epoch | 分代年齡 | 1 | 01 |
重量級鎖 | 指向監視器(monitor)的指針 | 10 | |||
GC標記 | 0 | 11 |
注:最后兩位為鎖標記位,倒數第三位是偏向標記,如果是1表示是偏向鎖;合並單元格的位數就是 該字段的位數,例如hash code共25(23+2)位。
另外,對於偏向鎖,如果Thread ID = 0,表示未加鎖
JVM鎖的類型及對比
Java 1.6對synchronized
進行了大幅度的優化,其性能也有了大幅度的提升。Java 1.6引入 “偏向鎖”和“輕量級鎖”的概念,減少了獲得鎖和釋放鎖的消耗。在Java 1.6之后,加上原有的重量 級鎖,鎖一共有4種狀態,分別是:無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態。鎖只能 按照上述的順序進行升級操作,鎖只要升級之后,就不能降級。
下面將分別介紹一下偏向鎖、輕量級鎖和重量級鎖,並探索一下偏向鎖升級為輕量級鎖(revoke bias
) 的流程和輕量級鎖升級為重量級鎖(inflate
)的流程。偏向鎖、輕量級鎖的狀態轉化及對象 Mark Word的關系如下圖所示。圖片來源:Synchronization and Object Locking文章中的配圖
圖1 偏向鎖、輕量級鎖的狀態轉化及對象Mark Word的關系
1. 偏向鎖
偏向鎖是Java 1.6新添加的內容,並且是jdk默認啟動的選項,可以通過-XX:-UseBiasedLocking
來關閉偏向鎖。另外,偏向鎖默認不是立即就啟動的,在程序啟動后,通常有幾秒的延遲,可以通過命令 -XX:BiasedLockingStartupDelay=0
來關閉延遲。
如果JVM支持偏向鎖,那么將按照下圖所示的流程分配對象,加偏向鎖。圖片來源:Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing第3頁的配圖
圖2 偏向鎖中的Mark Word的狀態轉化圖
注意:這是簡化版的流程圖,因為偏向鎖的圖中缺少了epoch
字段。
1.1 偏向鎖的加鎖
如果JVM支持偏向鎖,那么在分配對象時,分配一個可偏向而未偏向的對象(Mark Word的最后3位 為101,並且Thread ID
字段的值為0)。
然后,當一個線程訪問同步塊並獲取鎖時,將通過CAS(Compare And Swap)來嘗試將對象頭中的 Thread ID
字段設置為自己的線程號,如果設置成功,則獲得鎖,那么以后線程再次進入和退出 同步塊時,就不需要使用CAS來獲取鎖,只是簡單的測試一個對象頭中的Mark Word字段中是否存儲 着指向當前線程的偏向鎖;如果使用CAS設置失敗時,說明存在鎖的競爭,那么將執行偏向鎖的撤銷操作 (revoke bias
),將偏向鎖升級為輕量級鎖。
注:代碼請查看biasedLocking.cpp中的revoke_and_rebias方法。
1.2 偏向鎖的升級
下面結合代碼(有縮減)分析一下偏向鎖升級為輕量級鎖的過程,這里暫時不考慮批量撤銷偏向 (bulk revocation
)的情況。詳細代碼請查看biasedLocking.cpp中的revoke_bias方法。 偏向鎖的撤銷操作需要在全局檢查點(global safepoint
)執行,在全局檢查點上沒有 線程執行字節碼。
注:偏向鎖的撤銷的入口函數是biasedLocking.cpp中的revoke方法, 之后會通過VMThread
調用revoke_bias方法。
static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) { markOop mark = obj->mark(); // 檢查是否可偏向 if (!mark->has_bias_pattern()) { return BiasedLocking::NOT_BIASED; } uint age = mark->age(); 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) { // 可偏向但是未偏向的情況 // 可能的使用場景為:因計算hash code而撤銷偏向 if (!allow_rebias) { obj->set_mark(unbiased_prototype); } return BiasedLocking::BIAS_REVOKED; } // 判斷對象現在偏向的線程是否還存在 // 即對象頭中Mark Word中Thread ID字段指向的線程是否存在 bool thread_is_alive = false; if (requesting_thread == biased_thread) { // 請求的線程擁有偏向鎖 thread_is_alive = true; } else { // 請求的線程不擁有偏向鎖,遞歸查詢 for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) { if (cur_thread == biased_thread) { thread_is_alive = true; break; } } } if (!thread_is_alive) { if (allow_rebias) { obj->set_mark(biased_prototype); } else { obj->set_mark(unbiased_prototype); } return BiasedLocking::BIAS_REVOKED; } // 擁有偏向鎖的線程仍然存活 // 檢查該線程是否擁有鎖: // 如果擁有鎖,那么需要升級為輕量級鎖,然后將displaced mark word復制到線程棧中; // 如果不再擁有鎖,如果允許重偏向,那么將mark word中的Thread ID 重新置0; // 如果不允許重偏向,那么將mark work設置為無鎖狀態,即最后兩位為01 // cached_monitor_info 是該線程擁有的鎖對象的信息,按照從加鎖順序的逆序排列 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 =