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

java物件的記憶體佈局

1物件的記憶體佈局

1普通物件例項與陣列物件例項的資料結構圖

這裡寫圖片描述

或者

2 在HotSpot虛擬機器中,物件在記憶體中儲存的佈局如下

  • 物件頭(Header):
    • markword:用於儲存物件自身的執行時資料,如雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等,這部分資料的長度在32位和64位的虛擬機器(未開啟壓縮指標)中分別為32bit和64bit。
    • klass :物件指向它的類元資料的指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項。並不是所有的虛擬機器實現都必須在物件資料上保留型別指標,換句話說查詢物件的元資料資訊並不一定要經過物件本身。 
    • 陣列長度(只有陣列物件有) :如果物件是一個數組,那在物件頭中還必須有一塊資料用於記錄陣列長度。
      • 因為虛擬機器可以通過普通Java物件的元資料資訊確定Java物件的大小,但是從陣列的元資料中無法確定陣列的大小。
  • 例項資料(Instance Data):
    • 是物件真正儲存的有效資訊,也是在程式程式碼中所定義的各種型別的欄位內容。
    • 無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來。
    •  這部分的儲存順序會受到虛擬機器分配策略引數(FieldsAllocationStyle)和欄位在Java原始碼中定義順序的影響。HotSpot虛擬機器 預設的分配策略為longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers),從分配策略中可以看出,相同寬度的欄位總是被分配到一起。在滿足這個前提條件的情況下,在父類中定義的變數會出現在子類之前。如果 CompactFields引數值為true(預設為true),那子類之中較窄的變數也可能會插入到父類變數的空隙之中。(可看
      https://blog.csdn.net/zqz_zqz/article/details/70246212
  • 對齊填充(Padding):
    • 齊填充並不是必然存在的,也沒有特別的含義,它僅僅起著佔位符的作用。
    • 由於HotSpot VM的自動記憶體管理系統要求物件起始地址必須是8位元組的整數倍,換句話說,就是物件的大小必須是8位元組的整數倍。而物件頭部分正好是8位元組的倍數(1倍或者2倍),因此,當物件例項資料部分沒有對齊時,就需要通過對齊填充來補全。
       

3物件大小計算

  1. 在32位系統下,存放Class指標的空間大小是4位元組,MarkWord是4位元組,物件頭為8位元組。 
  2. 在64位系統下,存放Class指標的空間大小是8位元組,MarkWord是8位元組,物件頭為16位元組。 
  3. 64位開啟指標壓縮的情況下,存放Class指標的空間大小是4位元組,MarkWord是8位元組,物件頭為12位元組。 陣列長度4位元組+陣列物件頭8位元組(Class指標4位元組(未開啟指標壓縮的64位為8位元組)+陣列markword為4位元組(64位未開啟指標壓縮的為8位元組))+對齊4=16位元組。 
  4. 靜態屬性不算在物件大小內。

4 markword補充

物件需要儲存的執行時資料很多,其實已經超出了32、64位Bitmap結構所能記錄的限度,但是物件頭資訊是與物件自身定義的資料無關的額 外儲存成本,考慮到虛擬機器的空間效率,Mark Word被設計成一個非固定的資料結構以便在極小的空間記憶體儲儘量多的資訊,它會根據物件的狀態複用自己的儲存空間。

HotSpot虛擬機器中物件未被鎖定的狀態下結構如下(32位):

  • 25Bits用於儲存物件雜湊碼(HashCode)。
  • 4Bits用於儲存物件分代年齡。
  • 2Bits用於儲存鎖標誌 位。
  • 1Bit固定為0。

HotSpot虛擬機器物件頭Mark Word

儲存內容 標誌位 狀態
物件雜湊碼、物件分代年齡 01 未鎖定
指向鎖記錄的指標 00 輕量級鎖定
指向重量級鎖的指標 10 膨脹(重量級鎖定)
空,不需要記錄資訊 11 GC標記
偏向執行緒ID、偏向時間戳、物件分代年齡 01 可偏向

​​​​​​​除了上面以外的結構如下(32位):

5 HotSpot物件模型

5.1HotSpot中採用了OOP-Klass模型,它分為兩部分:

  • klass:類被載入到記憶體時,就被封裝成了klass,klass包含類的元資料資訊,像類的方法、常量池這些資訊都是存在klass裡的,你可以認為它是java裡面的java.lang.Class物件,記錄了類的全部資訊;
  • OOP(Ordinary Object Pointer):指的是普通物件指標,它包含MarkWord 和元資料指標,MarkWord用來儲存當前指標指向的物件執行時的一些狀態資料;元資料指標則指向klass,用來告訴你當前指標指向的物件是什麼型別,也就是使用哪個類來創建出來的;

5.2為何要設計這樣一個一分為二的物件模型呢?

這是因為HotSopt JVM的設計者不想讓每個物件中都含有一個vtable(虛擬函式表),所以就把物件模型拆成klass和oop,其中oop中不含有任何虛擬函式,而klass就含有虛擬函式表,可以進行method dispatch。

oop的實現為instanceOop 和 arrayOop,他們來描述物件頭,其中arrayOop物件用於描述陣列型別。

以下就是/hotspot/src/share/vm/oops/oop.hhp檔案中oopDesc的原始碼,其中

volatile markOop _mark;  //代表MarkWord,標識執行時資料
  union _metadata //表示元資料指標,指向klass物件,這個指標壓縮的是32位,未壓縮的是64位;
  {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;  //klass指標

5.3一個Java物件在記憶體中的佈局可以連續分成兩部分:instanceOop(繼承自oop.hpp)和例項資料;

這裡寫圖片描述

上圖可以看到,

  1. 通過棧幀中的物件引用reference找到Java堆中的物件。
  2. 再通過物件的instanceOop中的元資料指標klass來找到方法區中的instanceKlass,從而確定該物件的型別。

5.4 執行new A()的時候,JVM 做了什麼工作?

  1. 如果這個類沒有被載入過,JVM就會進行類的載入,並在JVM內部建立一個instanceKlass物件表示這個類的執行時元資料(相當於Java層的Class物件)。
  2. 初始化物件的時候(執行invokespecial A::),JVM就會建立一個instanceOopDesc物件表示這個物件的例項,然後進行Mark Word的填充,將元資料指標指向Klass物件,並填充例項變數。

元資料instanceKlass 物件會存在元空間(方法區),而物件例項instanceOopDesc 會存在Java堆。Java虛擬機器棧中會存有這個物件例項的引用。

6成員變數重排序

6.1為了提高效能,每個物件的起始地址都對齊於8位元組,當封裝物件的時候為了高效率,物件欄位宣告的順序會被重排序成下列基於位元組大小的順序:

  1. double (8位元組) 和 long (8位元組)
  2. int (4位元組) 和 float (4位元組)
  3. short (2位元組) 和 char (2位元組):char在java中是2個位元組。java採用unicode,2個位元組(16位)來表示一個字元。
  4. boolean (1位元組) 和 byte (1位元組)
  5. reference引用 (4/8 位元組)
  6. <子類欄位重複上述順序>