Java 對象布局及其組成
在 hotspot 虛擬機中,對象在內存中布局可以被分為三部分:對象頭/實例數據/補位數據。下面一張圖是一個普通 java 對象和一個數組對象的結構組成:
Java 對象組成
Hotspt 采用了 OOP-Klass 模型。 它是描述 java 對象實例的模型,可分為兩部分:
- OOP (Ordinary Object Pointer)指的是普通對象指針,它包含 MarkWord 和Klass 指針。MarkWord 用於存儲當前對象運行時的一些狀態數據;Klass 指針則指向 Klass,用來告訴當前指針指向的對象是什么類型,即對象是使用哪個類創建出來的
- 之所以采用這種一分為二的對象模型,是因為 hotspot jvm 的設計者不想讓每個對象中都包含一個 virtual table (虛函數表), 所以把對象模型拆成 klass 和 oop,其中 oop 不包含任何虛函數,而 klass 含有虛函數表,可以進行method dispatch
對象的模型如下:
volatile markOop _mark; //標識運行時數據
union _metadata {
Klass* _klass;
narrowKlass _compressed_klass;
} _metadata; //klass指針
對象頭
對象頭主要有兩部分(數組對象有三組分)組成。 Markword, Klass 指針(數組對象的話,還有一個 length)。
MarkWord
標記字主要存儲對象運行時的一部分數據。主要內容有 hashcode,GC 分代年齡,鎖狀態標志位,線程鎖標記,偏向線程ID,偏向時間戳等。MarkWord 在32位和64位虛擬機上的大小分別位32bit 和 64bit,它的最后 2 bit 是鎖標志位,用來標記當前對象的狀態,具體如下:
狀態 | 標志位 | 存儲內容 |
---|---|---|
未鎖定 | 01 | 對象哈希碼/對象分代年齡 |
輕量級鎖定 | 00 | 指向鎖記錄的指針 |
膨脹(重量級鎖定) | 10 | 執行重量級鎖定的鎖指針 |
GC 標記 | 11 | 空(不需要記錄信息) |
可偏向 | 01 | 偏向線程id, 偏向時間戳,對象分代年齡 |
32 位 vm 在不同狀態下 Markword 結構如下:
OBJECT HEADER | state | |
---|---|---|
Mark word (32 bit) | Class Word(32 bits) | |
identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | OOP to metadata object | Normal |
thread:23 | epoch:2 | age: 4 | biased_lock:1 | lock:2 | OOP to metadata object | Biased |
ptr_to_lock_record: 30 | lock:2 | OOP to metadata object | Lightweight Locked |
ptr_to_heavyweight_monitor: 30 | lock:2 | OOP to metadata object | Heavyweight Locked |
| lock:2 | OOP to metadata object | Marked For GC |
Klass 指針(元數據指針)
Klass 主要指向對象所屬的類信息(class metadata)。虛擬機使用它來確定當前對象屬於哪個類。klass 包含類的元數據信息,像類的方法,常量池等。你可以把它當作 java 里的 java.lang.Class 對象
數組長度
如果這個對象是數組類對象,那么如上圖右側所示,會在對象頭里額外的添加一個記錄數組長度的字段
實例數據
這部分主要存儲的是對象實際的數據
對齊填充
hotspot vm 的自動內存管理系統要求對象起始地址比必須位 8 字節的整數倍,即對象的大小必須是 8 字節的整數倍。而對象頭部分正好是 8 字節的整數倍,因此,當對象實例數據部分沒有對齊時,此時需要對齊填充來補齊
Java 對象大小計算
首先,對象頭大小的確定
- 在32位系統中,存放 Class 的指針空間大小為 4 字節,MarkWord 為 4 字節,即對象頭為 8 字節
- 在64 位系統中,存放 Class 指針的空間大小為 8 字節, MarkWord 為 8 字節,即對象頭大小為 8 字節
- 64 位系統下,如果開啟了指針壓縮,存放 Class指針的空間大小為 4 字節,MarkWord 為 8 字節,即對象頭為 12 字節
- 對於對象數據結構,參照上述三個規則,額外加上一個數組長度所含用的 4 個字節,就是最終對象頭的大小
- 計算對象的大小時,靜態數據是不會被考慮進去的
有很多中方法計算,這里我們使用 jol 進行計算
首先假設我們待計算的對象所屬的 Class 結構如下:
public class JOLSample_10_DataModels {
/*
* This example shows the differences between the data models.
*
* First layout is the actual VM layout, the remaining three
* are simulations. You can see the reference sizes are different,
* depending on VM bitness or mode. The header sizes are also
* a bit different, see subsequent examples to understand why.
*/
public static void main(String[] args) throws Exception {
Layouter l;
l = new CurrentLayouter();
System.out.println("***** " + l);
System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());
l = new HotSpotLayouter(new X86_32_DataModel());
System.out.println("***** " + l);
System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());
l = new HotSpotLayouter(new X86_64_DataModel());
System.out.println("***** " + l);
System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());
l = new HotSpotLayouter(new X86_64_COOPS_DataModel());
System.out.println("***** " + l);
System.out.println(ClassLayout.parseClass(A.class, l).toPrintable());
}
public static class A {
Object a;
Object b;
}
}
其在不同環境下的內存布局如下:
參考: