🏆【JVM技術專區】「難點-核心-遺漏」TLAB記憶體分配+鎖的碰撞(技術串燒)!

JVM記憶體分配及申請過程

當使用new關鍵字或者其他任何方式進行創建一個類的對象時,JVM虛擬機需要為該對象分配記憶體空間,而對象的大小在類載入完成後已經確定了,所以分配記憶體只需要在Java堆中劃分出一塊大小相等的記憶體,JVM虛擬機中有指針碰撞和空閑列表兩種方式分配記憶體。

指針碰撞方式

如果Java堆中記憶體是規整排列的,所有被用過的記憶體放一邊,空閑的可用記憶體放一邊,中間放置一個指針作為它們的分界點,在需要為新生對象分配記憶體的時候,只要將指針向空閑記憶體那邊挪動一段與對象大小相等的距離即可分配。

代表GC回收器

ParNew,Serial,G1

空閑列表方式

如果Java堆中記憶體不是規整排列的,用過的記憶體和可用記憶體是相互交錯的,這種情況下將不能使用指針碰撞方式分配記憶體,Java虛擬機需要維護一個列表用於記錄哪些記憶體是可用的,在為新生對象分配記憶體的時候,在列表中尋找一塊足夠大的記憶體分配,並更新列表上的記錄。

代表GC回收器

cms

Java虛擬機選擇策略

Java虛擬機採用哪種方式為新生對象分配記憶體,取決於所使用的垃圾收集器,當垃圾收集器具有整理過程時,虛擬機將採用指針碰撞的方式;當垃圾收集器的回收過程沒有整理過程時,則採用空閑列表方式。

現在虛擬機棧進行分配

此部分屬於兩部分的分配機制,當JVM創建執行緒Thread對象:

  1. 直接分配:局部變數、形式參數表。

  2. 優化分配:逃逸分析(棧上分配、標量替換等功能)。

如果完成分配之後,則結束記憶體分配,否則出現分配失敗,或者無法進行分配操作後,會進入堆記憶體方式的分配。

新生區-Eden區的分配

TLAB記憶體的分配策略

上面剛剛說過了,主要有兩種記憶體分配機制:如果採用指針碰撞法,則會出現性能問題和指針分配衝突的問題.,JVM虛擬機採用優化的手段,就是TLAB(ThreadLocal Allocation Buffer)預先分配了記憶體塊。

總體記憶體分配流程策略

如果TLAB記憶體分配失敗或者空間不足,則JVM會試圖為相關Java對象在Eden中初始化一塊記憶體區域,當Eden空間足夠時,記憶體申請結束

當如果出現了Eden區記憶體無法進行分配,則會發生相關MinorGC(JVM試圖釋放在Eden中所有不活躍的對象(Minor GC),釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區)。

此時Survivor區被用來作為Eden及old的中間交換區域,如果Survivor不足以放置eden區的對象 ,會進行擔保分配,或者已經達到直接晉陞到老年代的條件後,此時如果old區有空閑,Survivor區的對象會被移到Old區。

當old區空間不夠時,JVM會在old區進行major collection;

完全垃圾收集後,若Survivor及old區仍然無法存放從Eden複製過來的部分對象,導致JVM無法在Eden區為新對象創建記憶體區域,則出現”Out of memory錯誤”;

  1. jvm優先分配在eden區
  2. 當Eden空間足夠時,記憶體申請結束。

JVM鎖的膨脹執行流程機制

無鎖節點/偏向鎖階段

創建執行緒的時候在程式執行到同步程式碼塊的時候,首先會基於上面說到的記憶體分配策略進行分配記憶體,此時會噹噹前執行緒獲取到了相關鎖資源的時候,因為屬於無鎖狀態下轉換為偏向鎖:

無鎖標記頭

偏向鎖標記頭

  1. 會將相關的當前執行緒的執行緒ID賦值到相關的標記欄位中。
  2. 為了提高性能以及棧空間可以獲取相關的競爭數據,會將對象頭的標記欄位(Markword)拷貝到棧空間內部(Lock Record)鎖記錄。

輕量級鎖階段

競爭到鎖的執行緒

與此同時,當另外一個執行緒同時也去競爭該資源的時候,需要進行競爭,因為在獲取資源的時候,底層採用CAS機製取獲取相關的資源標誌,一旦獲取成功,便可以通過偏向鎖標識進行判斷是否屬於當前的鎖owner執行緒。

當發現不屬於偏向鎖的執行緒進來競爭的時候,此時會產生競爭關係,因為同一時刻,只能允許一個執行緒獲取資源,當前獲取資源的執行緒會因為有其他執行緒也爭搶過該資源,故此將java對象頭中的鎖欄位改為00,如下圖所示:

未競爭到鎖的執行緒

當發生執行緒爭搶CAS機制失敗的時候,會進行相關的自旋機制,進行嘗試下一次進行爭搶到鎖。


重量級鎖階段

未競爭到鎖的執行緒
  1. 當超過自旋的執行緒一直處於自旋,且超過了自旋閾值之後,變會升級成為了重量級鎖。

  2. 當更多的執行緒都處於爭搶狀態且屬於自旋鎖機制之後(出現了大量的輕量級鎖之後),便會升級未重量級鎖。

  3. 直到被喚醒重新鏡像競爭鎖資源資訊。

升級為重量級鎖的結果會將執行緒的標誌位置為10

此時不會在進行自旋CAS爭搶 ,而是直接阻塞執行(採用底層mutex/Fast Mutex鎖進行暫停中斷執行緒的執行)。

競爭到鎖的執行緒
  • 當鎖標記因為發生變化,成為了重量級鎖,所以,執行緒會同步自己的所記錄,發現不一致,同步為重量級鎖狀態後,釋放鎖之後,進行喚醒阻塞的狀態的執行緒。

  • 鎖的狀態暫時處於重量級鎖狀態。接下來會專門寫一篇文章講解一下鎖降級哦,鎖降級會較為複雜,而且場景完全不一樣,對JVM要求也不一樣。

當對象出現消亡了回收狀態: