Java對象頭與鎖


對象由多部分構成的,對象頭,屬性字段、補齊區域等。所謂補齊區域是指如果對象總大小不是4字節的整數倍,會填充上一段內存地址使之成為整數倍。

后面兩個很好理解,今天我主要想總結一下對象頭:

對象頭這部分在對象的最前端,包含兩部分或者三部分:Mark Words、Klass Words,如果對象是一個數組,那么還可能包含第三部分:數組的長度。

 


Klass Word里面存的是一個地址,占32位或64位,是一個指向當前對象所屬於的類的地址,可以通過這個地址獲取到它的元數據信息。


 Mark Word需要重點說一下,這里面主要包含對象的哈希值、年齡分代、鎖標志位等,大小為32位或64位

當對象處於不同的鎖狀態時,它的Mark Word里的東西會被替換成不同東西,如下表所示:

 

1、對象未加鎖的時候,lock標志位為01,包含哈希值、年齡分代和偏向鎖標志位等,此時偏向鎖標志位為0;

2、當對象被施加偏向鎖時,哈希值和一部分無用內存會轉化為鎖主人的線程信息,以及加鎖的時間戳epoch,此時lock標志位沒變,偏向鎖為1,也就是說,偏向鎖和lock標志位共同決定是否偏向鎖狀態。

偏向鎖的加鎖步驟:

  • Load-and-test,也就是簡單判斷一下當前線程id是否與Markword當中的線程id是否一致.
  • 如果一致,則說明此線程已經成功獲得了鎖,繼續執行下面的代碼.
  • 如果不一致,則要檢查一下對象是否還是可偏向,即“是否偏向鎖”標志位的值。
  • 如果還未偏向,則利用CAS操作來競爭鎖,也即是第一次獲取鎖時的操作。

3、當發生鎖競爭時,偏向鎖會變為輕量級鎖,這時需要先將偏向鎖進行鎖撤銷,這一步驟也會消耗不少的性能,輕量級鎖的Mark Word中,lock標志位為00,其余內容被替換為一個指針,指向了棧里面的鎖記錄。

鎖撤銷的過程如下:

  • 在一個安全點停止擁有鎖的線程。
  • 遍歷線程棧,如果存在鎖記錄的話,需要修復鎖記錄和Markword,使其變成無鎖狀態。
  • 喚醒當前線程,將當前鎖升級成輕量級鎖。

所以,如果某些同步代碼塊大多數情況下都是有兩個及以上的線程競爭的話,那么偏向鎖就會是一種累贅,對於這種情況,我們可以一開始就把偏向鎖這個默認功能給關閉

輕量級鎖的加鎖步驟:

  • 線程在自己的棧楨中創建鎖記錄LockRecord。
  • 將鎖對象的對象頭中的MarkWord復制到線程的剛剛創建的鎖記錄中。
  • 將鎖記錄中的Owner指針指向鎖對象。
  • 將鎖對象的對象頭的MarkWord替換為指向鎖記錄的指針。

輕量級鎖主要有兩種:自旋鎖和自適應自旋鎖。自旋鎖會導致空耗CPU且很可能鎖不公平;自適應是指根據上一次該線程是否成功或者多久獲取過該鎖設置旋轉次數,若上次失敗很可能直接進入重量級鎖

4、如果競爭線程增多,鎖繼續膨脹,變為重量級鎖,也是互斥鎖,即synchronized,其lock標志位為10,Mark Word其余內容被替換為一個指向對象監視器Monitor的指針。

特殊的是,如果此對象已經被GC標記過,lock會變為11,不含其余內容。


 

Monitor對象

每個對象都有一個Monitor對象相關聯,Monitor對象中記錄了持有鎖的線程信息、等待隊列等。Monitor對象包含以下三個字段:

  • _owner 記錄當前持有鎖的線程
  • _EntryList 是一個隊列,記錄所有阻塞等待鎖的線程
  • _WaitSet 也是一個隊列,記錄調用 wait() 方法並還未被通知的線程

當線程持有鎖的時候,線程id等信息會拷貝進owner字段,其余線程會進入阻塞隊列entrylist,當持有鎖的線程執行wait方法,會立即釋放鎖進入waitset,當線程釋放鎖的時候,owner會被置空,公平鎖條件下,entrylist中的線程會競爭鎖,競爭成功的線程id會寫入owner,其余線程繼續在entrylist中等待。


 

Monitor與Synchronized

對於Synchronized的同步代碼塊,JVM會在進入代碼塊之前加上monitorenter ,如果進入monitor成功,線程便獲取了鎖,一個對象的monitor同一時刻只能被一個線程鎖占有;

對於同步方法,JVM會講方法設置 ACC_SYNCHRONIZED 標志,調用的時候 JVM 根據這個標志判斷是否是同步方法。

采用Synchronized給對象加鎖會使線程阻塞,因而會造成線程狀態的切換,而線程狀態的切換必須要操作系統來執行,因此需要將用戶態切換為內核態,這個切換的過程是十分耗時的都需要操作系統來幫忙,有可能比用戶執行代碼的時間還要長。


 Synchronized是JVM級別的鎖,它在不斷被優化着,從目前來看Synchronized已經遠沒有以前那么“重”了,也大概就是JUC包源碼(如ConcurrentHashMap)中大量使用Synchronized的原因把

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM