java對象頭由3部分組成:
1、Mark Word
2、指向類對象(對象的class對象)的指針
3、數組長度(數組類型才有)
重點是 Mark Word結構,下面以32位HotSpot為例:
一、偏向鎖
1、概念:
HotSpot的作者經過研究發現,大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低從而引入偏向鎖。偏向鎖在獲取資源的時候會在鎖對象頭上記錄當前線程ID,偏向鎖並不會主動釋放,這樣每次偏向鎖進入的時候都會判斷鎖對象頭中線程ID是否為自己,如果是則不需要進行額外的操作,直接進入同步操作。
2、偏向鎖的獲取過程:
I:判斷是否為可偏向狀態--MarkWord中鎖標志是否為‘01’,是否偏向鎖是否為‘1’
II:如果是可偏向狀態,則查看線程ID是否為當前線程,如果是,則進入步驟'V',否則進入步驟‘III’
III:通過CAS操作競爭鎖,如果競爭成功,則將MarkWord中線程ID設置為當前線程ID,然后執行‘V’;競爭失敗,則執行‘IV’
IV:CAS獲取偏向鎖失敗表示有競爭。當達到safepoint時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然后被阻塞在安全點的線程繼續往下執行同步代碼塊
V:執行同步代碼
3、偏向鎖的撤銷過程:
I:偏向鎖不會主動釋放(撤銷),只有遇到其他線程競爭時才會執行撤銷,由於撤銷需要知道當前持有該偏向鎖的線程棧狀態,因此要等到safepoint時執行,此時持有該偏向鎖的線程(T)有‘II’,‘III’兩種情況;
II:撤銷----T線程已經退出同步代碼塊,或者已經不再存活,則直接撤銷偏向鎖,變成無鎖狀態----該狀態達到閾值20則執行批量重偏向
III:升級----T線程還在同步代碼塊中,則將T線程的偏向鎖升級為輕量級鎖,當前線程執行輕量級鎖狀態下的鎖獲取步驟----該狀態達到閾值40則執行批量撤銷
4、批量重偏向/撤銷:
從‘3、偏向鎖的撤銷過程’可以看出偏向鎖需要等到safepoint才能進行鎖升級/撤銷,這種情況偏向鎖不僅不能提高性能,反而會導致性能下降,思考兩個場景:
I:線程T1創建了大量對象,並進行初始的同步操作,這時這些對象都偏向T1;之后線程T2使用這些對象作為鎖進行同步操作,則會存在大量的偏向鎖撤銷操作
II:存在明顯多線程鎖競爭時會存在大量偏向鎖升級過程,為了解決這兩個問題,jvm提供了批量重偏向/撤銷的機制
批量重偏向/撤銷過程:
I:首先引入一個概念epoch,其本質是一個時間戳,代表了偏向鎖的有效性,epoch存儲在可偏向對象的MarkWord中。除了對象中的epoch,對象所屬的類class信息中,也會保存一個epoch值
II:每當遇到一個全局安全點時(這里的意思是說批量重偏向沒有完全替代了全局安全點,全局安全點是一直存在的),比如要對class C 進行批量再偏向,則首先對 class C中保存的epoch進行增加操作,得到一個新的epoch_new
III:然后掃描所有持有 class C 實例的線程棧,根據線程棧的信息判斷出該線程是否鎖定了該對象,僅將epoch_new的值賦給被鎖定的對象中,也就是現在偏向鎖還在被使用的對象才會被賦值epoch_new
IV:退出安全點后,當有線程需要嘗試獲取偏向鎖時,直接檢查 class C 中存儲的 epoch 值是否與目標對象中存儲的 epoch 值相等, 如果不相等,則說明該對象的偏向鎖已經無效了(即用完了,因為'III'步驟里面已經說了只有偏向鎖還在被使用的對象才會有epoch_new,這里不相等的原因是class C里面的epoch值是epoch_new,而當前對象的epoch里面的值還是epoch),此時競爭線程可以嘗試對此對象重新進行偏向操作,即通過CAS操作將其Mark Word的Thread Id 改成當前線程Id
V:當epoch達到重偏向閾值(默認20)時,jvm就認為該class的偏向鎖偏向的線程有問題,因此會進行批量重偏向。當epoch達到批量撤銷閾值(默認40)時,jvm就認為這個class不再適合偏向鎖,就會批量撤銷,並且在之后的加鎖過程中直接為該class的對象設置輕量級鎖
二、輕量級鎖
1、概念:
輕量級鎖是相對於重量級鎖需要阻塞/喚醒涉及上下文切換而言,主要針對多個線程在不同時間請求同一把鎖的場景。
2、輕量級鎖獲取過程:
I:進行加鎖操作時,jvm會判斷是否已經時重量級鎖,如果不是,則會在當前線程棧幀中划出一塊空間,作為該鎖的鎖記錄,並且將鎖對象MarkWord復制到該鎖記錄中
II:復制成功之后,jvm使用CAS操作將對象頭MarkWord更新為指向鎖記錄的指針,並將鎖記錄里的owner指針指向對象頭的MarkWord。如果成功,則執行‘III’,否則執行‘IV’
III:更新成功,則當前線程持有該對象鎖,並且對象MarkWord鎖標志設置為‘00’,即表示此對象處於輕量級鎖狀態
IV:更新失敗,jvm先檢查對象MarkWord是否指向當前線程棧幀中的鎖記錄,如果是則執行‘V’,否則執行‘VI’
V:表示鎖重入;然后當前線程棧幀中增加一個鎖記錄第一部分(Displaced Mark Word)為null,並指向Mark Word的鎖對象,起到一個重入計數器的作用。
VI:表示該鎖對象已經被其他線程搶占,則進行自旋等待(默認10次),等待次數達到閾值仍未獲取到鎖,則升級為重量級鎖
3、輕量級鎖解鎖過程:
I:通過CAS操作把嘗試把線程棧幀中復制的鎖記錄中的Displaced Mark Word替換當前對象頭的MarkWord(即還原對象頭)
II:替換成功則同步塊執行順利結束
III:替換失敗說明已經膨脹為重量級鎖,則在執行完同步塊釋放鎖同時喚醒被掛起的線程
4、自適應自旋:
根據以往自旋等待時是否能夠獲得鎖,來動態調整自旋的時間(循環數目)
三、重量級鎖
1、重量級鎖加鎖過程:
I:調用omAlloc分配一個ObjectMonitor對象,把Mark Word鎖標志置為‘10’,然后Mark Word存儲指向ObjectMonitor對象的指針。ObjectMonitor對象有兩個隊列和一個指針,每個需要獲取鎖的線程都包裝成ObjectWaiter對象
II:多個線程同時執行同一段同步代碼時,ObjectWaiter先進入_EntryList隊列,當某個線程獲取到對象的monitor以后進入_Owner區域,並把monitor中的owner變量設置為當前線程同時monitor中的計數器count+1;
2、重量級鎖釋放過程:
I:若同步塊中的線程調用wait()方法,則釋放持有的monitor,owner遍歷置為null,count-1,同時線程進入_WaitSet等待被喚醒
II:若當前同步塊執行完畢,則也釋放持有的monitor,owner遍歷置為null,count-1