JVM中鎖優化簡介

y37f 9年前發布 | 13K 次閱讀 JVM Java開發

原文出處: stackvoid.com


本文將簡單介紹HotSpot虛擬機中用到的鎖優化技術。

自旋鎖

互斥同步對性能最大的影響是阻塞的實現,掛起線程和恢復線程的操作都需要轉入內核態中完成,這些操作給系統的并發性能帶來了很大的壓力。而在很多應 用上,共享數據的鎖定狀態只會持續很短的一段時間。若實體機上有多個處理器,能讓兩個以上的線程同時并行執行,我們就可以讓后面請求鎖的那個線程原地自旋 (不放棄CPU時間),看看持有鎖的線程是否很快就會釋放鎖。為了讓線程等待,我們只須讓線程執行一個忙循環(自旋),這項技術就是自旋鎖。

如果鎖長時間被占用,則浪費處理器資源,因此自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去掛起線程了(默認10次)。

JDK1.6引入自適應的自旋鎖:自旋時間不再固定,由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。如果在同一個鎖對象上,自旋等待剛 剛成功獲得過鎖,并且持有鎖的線程正在運行中,那么虛擬機就會認為這次自旋也很有可能再次成功,進而它將允許自旋等待持續相對更長的時間。

鎖削除

鎖削除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行削除(主要判定依據來源于逃逸分析的數據 支持,如果判斷到一段代碼中,在堆上的所有數據都不會逃逸出去被其他線程訪問到,那就可以把它們當作棧上數據對待,認為它們是線程私有的,同步加鎖自然就 無須進行)。

鎖膨脹

如果一系列的連續操作都對同一個對象反復加鎖和解鎖,甚至加鎖操作是出現在循環體中的,那即使沒有線程競爭,頻繁地進行互斥同步操作也會導致不必要 的性能損耗。 如果虛擬機探測到有這樣一串零碎的操作都對同一個對象加鎖,將會把加鎖同步的范圍擴展(膨脹)到整個操作序列的外部(由多次加鎖編程只加鎖一次)。

輕量級鎖

輕量級鎖并不是用來代替重量級鎖(傳統鎖機制,如互斥等)的,目的是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用操作系統互斥量產生的性能消耗。

HotSpot虛擬機的對象頭(Object Header)分為兩部分信息,第一部分用于存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡(Generational GC Age)等,這部分數據的長度在32位和64位的虛擬機中分別為32個和64個Bits,官方稱它為“Mark Word”,它是實現輕量級鎖和偏向鎖的關鍵。另外一部分用于存儲指向方法區對象類型數據的指針,如果是數組對象的話,還會有一個額外的部分用于存儲數組 長度。

Mark Word被設計成一個非固定的數據結構以便在極小的空間內存儲盡量多的信息,它會根據對象的狀態復用自己的存儲空間。例如在32位的HotSpot虛擬機 中對象未被鎖定的狀態下,Mark Word的32個Bits空間中的25Bits用于存儲對象哈希碼(HashCode),4Bits用于存儲對象分代年齡,2Bits用于存儲鎖標志 位,1Bit固定為0,在其他狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向)下Mark Word的存儲內容如下表所示。

</tr> </tbody>

</tr>

</tr>

</tr>

</tr>

</tr> </tbody> </table>

加鎖過程

在代碼進入同步塊的時候,如果此同步對象沒有被鎖定(鎖標志位為“01”狀態),虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的Mark Word的拷貝(官方把這份拷貝加了一個Displaced前綴,即Displaced Mark Word),這時候線程堆棧與對象頭的狀態如下圖所示。

 JVM中鎖優化簡介

然后,虛擬機將使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針。如果這個更新動作成功,那么這個線程就擁有了該對象的鎖,并且對象Mark Word的鎖標志位(Mark Word的最后兩個Bits)將轉變為“00”,即表示此對象處于輕量級鎖定狀態,這時候線程堆棧與對象頭的狀態如下圖所示。

 JVM中鎖優化簡介

如果這個更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行,否則說明這個鎖對象已經被其他線程搶占 了。如果有兩條以上的線程爭用同一個鎖,那輕量級鎖就不再有效,要膨脹為重量級鎖,鎖標志的狀態值變為“10”,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也要進入阻塞狀態。

解鎖過程

解鎖過程也是通過CAS操作來進行的,如果對象的Mark Word仍然指向著線程的鎖記錄,那就用CAS操作把對象當前的Mark Word和線程中復制的Displaced Mark Word替換回來,如果替換成功,整個同步過程就完成了。如果替換失敗,說明有其他線程嘗試過獲取該鎖,那就要在釋放鎖的同時,喚醒被掛起的線程。

輕量級鎖小結

輕量級鎖能提升程序同步性能的依據是“對于絕大部分的鎖,在整個同步周期內都是不存在競爭的”,這是一個經驗數據。如果沒有競爭,輕量級鎖使用 CAS操作避免了使用互斥量的開銷,但如果存在鎖競爭,除了互斥量的開銷外,還額外發生了CAS操作,因此在有競爭的情況下,輕量級鎖會比傳統的重量級鎖 更慢。

偏向鎖

目的是消除數據在無競爭情況下的同步原語,進一步提高程序的運行性能。如果說輕量級鎖是在無競爭的情況下使用CAS操作去消除同步使用的互斥量,那偏向鎖就是在無競爭的情況下把整個同步都消除掉,連CAS操作都不做了。

偏向鎖會偏向于第一個獲得它的線程(Mark Word中的偏向線程ID信息),如果在接下來的執行過程中,該鎖沒有被其他的線程獲取,則持有偏向鎖的線程將永遠不需要再進行同步。

假設當前虛擬機啟用了偏向鎖(啟用參數-XX:+UseBiasedLocking,JDK 1.6的默認值),當鎖對象第一次被線程獲取的時候,虛擬機將會把對象頭中的標志位設為“01”,即偏向模式。同時使用CAS操作把獲取到這個鎖的線程的 ID記錄在對象的Mark Word之中,如果CAS操作成功,持有偏向鎖的線程以后每次進入這個鎖相關的同步塊時,虛擬機都可以不再進行任何同步操作(例如Locking、 Unlocking及對Mark Word的Update等)。          當有另外一個線程去嘗試獲取這個鎖時,偏向模式就宣告結束。根據鎖對象目前是否處于被鎖定的狀態,撤銷偏向(Revoke Bias)后恢復到未鎖定(標志位為“01”)或輕量級鎖定(標志位為“00”)的狀態,后續的同步操作就如上面介紹的輕量級鎖那樣執行。偏向鎖、輕量級 鎖的狀態轉化及對象Mark Word的關系如下圖所示。

 JVM中鎖優化簡介

偏向鎖可以提高帶有同步但無競爭的程序性能。它同樣是一個帶有效益權衡(Trade Off)性質的優化,也就是說它并不一定總是對程序運行有利,如果程序中大多數的鎖都總是被多個不同的線程訪問,那偏向模式就是多余的。

主要參考

  1. 《深入理解Java虛擬機:JVM高級特性與最佳實踐》
  2. The Hotspot Java Virtual Machine
  3. Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing
 本文由用戶 y37f 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
存儲內容 標志位 狀態
對象Hash值、對象分代年齡 01 未鎖定
指向鎖記錄的指針 00 輕量級鎖定
指向重量級鎖的指針 10 膨脹(重量級鎖定)
空,不記錄信息 11 GC標記
偏向線程ID、偏向時間戳、對象分代年齡 01 可偏向
  • sesese色