偏向鎖撤銷觸發STW(Stop-The-World)的根本原因在于其撤銷操作需要??全局內存一致性??和??線程狀態確定性??,具體機制如下:
?? ??一、偏向鎖撤銷的核心流程??
- ??競爭觸發撤銷??
當線程B嘗試獲取已被線程A偏向的鎖時,JVM檢測到競爭,觸發撤銷操作。 - ??掛起持有線程??
需暫停持有偏向鎖的線程A,檢查其是否仍在同步塊中:- 若線程A已退出同步塊:直接撤銷偏向鎖,恢復為無鎖狀態(標志位
01
)。 - 若線程A仍在同步塊中:升級為輕量級鎖(標志位
00
),線程B繼續競爭。
- 若線程A已退出同步塊:直接撤銷偏向鎖,恢復為無鎖狀態(標志位
- ??對象頭修改??
將對象頭(Mark Word)中的線程ID移除,替換為輕量級鎖指針或無鎖狀態。
🛑 ??二、STW的必要性??
撤銷操作必須在??全局安全點(Safepoint)?? 進行,原因如下:
- ??線程狀態一致性??
需確保所有線程(尤其是持有偏向鎖的線程)處于可安全掛起的狀態(如未執行字節碼),防止撤銷過程中線程修改對象頭或棧幀。 - ??線程棧遍歷需求??
判斷線程A是否在同步塊中,需遍歷其棧幀中的Lock Record
(鎖記錄)。若線程未暫停,棧幀可能動態變化,導致數據不一致。 - ??內存屏障要求??
對象頭修改需原子性,且其他線程必須立即可見。STW期間可避免CPU緩存與主存不一致問題。
?? ??三、STW的性能影響??
- ??短暫停頓??:單次撤銷通常耗時??微秒級??(如0.1-1ms),對低競爭場景影響小。
- ??高并發瓶頸??:頻繁競爭會導致多次撤銷,累積STW時間顯著增加(如百線程競爭時可達毫秒級)。
- ??日志示例??:
啟用安全點日志(-XX:+PrintSafepointStatistics
)可見:
其中RevokeBias [threads: 200 initially_running: 5] [time: spin=0.1ms block=2ms cleanup=0.5ms vmop=15ms]
vmop=15ms
為實際STW時間。
?? ??四、不同場景下的撤銷處理??
??場景?? | 處理方式 | 是否需STW |
---|---|---|
持有線程已退出同步塊 | 直接恢復為無鎖狀態 | 是(但操作更快) |
持有線程仍在同步塊中 | 升級為輕量級鎖 | 是 |
調用hashCode() /wait() | 強制撤銷并升級重量級鎖 | 是 |
調用
hashCode()
會覆蓋Mark Word中的線程ID,強制撤銷偏向鎖。
🚀 ??五、優化建議??
- ??禁用偏向鎖??
高并發場景(如秒殺系統)通過-XX:-UseBiasedLocking
禁用,避免頻繁撤銷導致的STW。 - ??監控安全點??
臨時開啟日志定位非GC導致的STW:-XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1
- ??升級JDK版本??
JDK 15+ 默認關閉偏向鎖,JDK 18+ 已移除該機制,徹底規避此問題。
💎 ??總結??
偏向鎖撤銷觸發STW的本質是??保證內存與線程狀態一致性??的代價。在單線程場景中,偏向鎖通過避免CAS操作提升性能;但在多線程競爭下,其撤銷機制反而成為瓶頸。??高并發系統應禁用偏向鎖??,選擇輕量級鎖(CAS)或直接使用java.util.concurrent
中的并發工具(如ReentrantLock
),以消除STW對延遲的影響。