1. 程式人生 > >JVM物件的記憶體佈局

JVM物件的記憶體佈局

摘要:博主JVM系列都是跟隨周志明先生的《深入瞭解JAVA虛擬機器》一書來學習,記錄並嘗試解釋下書中一些晦澀知識點,方便自己複習,如果能幫到他人,十分榮幸。

在HotSpot虛擬機器中,物件在記憶體中儲存的區域可以分為三個部分:物件頭(Header),例項資料(Instance Data),對齊填充(padding):

物件頭:

HotSpot虛擬機器中物件頭包含兩部分資訊:一部分是物件執行時的資料,如雜湊碼(HashCode),GC分代年齡,鎖狀態標誌,執行緒持有的鎖,偏向執行緒ID,偏向時間戳,這部分資料的長度在32bit和64bit的虛擬機器中,官方稱之為“Mark Word”.

  •       GC分代年齡:Java堆是用來存放java例項物件的,JVM將java堆分成了兩個不同的區域新生代(young)和老年代(old)。將新生代又分為了三個部分Eden,FromSurvivor,Tosurvivor,這主要是為了方便回收,至於如何回收,新生代老年代之間的轉化參考另一片部落格。。。。。。(明天編寫。。).
  •      鎖狀態標誌:鎖是為了解決多執行緒競爭的問題,在併發的情況下,可能出現多個執行緒同時搶佔一個資源並對資源操作的情況,為了保證原子性,就是用鎖,鎖狀態標誌位就是用字面意思咯!
  •       偏向執行緒ID這裡博主認為是偏向鎖指向的執行緒ID,簡稱偏向執行緒ID。因為為了保證原子性,在多執行緒的情況下,搶佔到CPU的執行緒在執行的前後都會進行上鎖和卸鎖的操作,有一定的時間消耗。但是如果此時鎖總是同一個執行緒擁有,很少發生競爭,我們就可以就說這個執行緒是鎖的偏向執行緒。那麼下一次執行該執行緒的時候,可以通過匹配的方式(檢視鎖偏向ID和程序ID是否一致)進入同步,無需進行繁瑣的加鎖和卸鎖過程。
  •        時間戳是一個數據型別,只是精度很高,避免出現髒讀現象。

物件需要儲存的資料很多,其實已經超過了32bit或64bitBitMap結構能夠記錄的極限,但是物件頭資訊是與物件自定義資料無關的額外儲存成本,考慮到虛擬機器的空間效率,Mark Word被設計成一種非固定資料以便在極小的空間記憶體儲更多的資訊,它會根據物件的狀態來複用儲存空間。

第二部分是型別指標,這是一個指向它的元資料的指標,即該物件是誰的例項。但是並不是所有的虛擬機器都必須實現在物件資料上保留型別指標,換句話說,查詢物件元資料資訊不一定通過物件本身。如果物件是一個數組型別,物件頭需要儲存該陣列的長度,因為虛擬機器可以通過普通的 Java物件的元資料資訊來確定Java物件的大小,但是無法從陣列的元資料中確定陣列大小。

        這裡博主斗膽的認為是一個數組Array={1,2,3,4},我們可以通過Array.length()來獲取陣列大小,但是通過Array[2](3) 陣列中資料則不行

例項資料

     例項資料就是物件真正儲存的有效資料,也就是程式程式碼中定義的各種型別的欄位內容。無論是從父類繼承的,或者自己定義的,都需要記錄起來。這部分資料的儲存順序並不是使用者定義的先後順序決定的,而是受到虛擬機器引數(FildsAllcationStyle)和欄位在java原始碼中定義的順序的影響。HotSpot虛擬機器預設的分配策略為longs/doubles,ints,shorts/chars,bytes/booleans,oops(Ordinary Object Pointers),從定義可以看出,相同欄位的總是分配到一起,在滿足這個條件下,父類中定義的變數會出現在子類變數之前。如果CompactFields引數值為true,那麼子類中較窄的變數也可能出現在父類變數的空隙之中。

對齊填充

      對齊填充並不是必然存在的,因為它僅僅起著佔位符的作用。由於HotSpotVm的自動記憶體管理要求物件起始地址必須是8位元組的整數倍,換句話說,就是物件的大小必須是8的整數倍。而物件頭部分剛好是8的整數倍,所以我們前面不做考慮,但是例項物件部分如果沒有對齊的話,就需要通過對齊填充來補全