2 Java對象記憶體模型
在HotSpot虛拟機中,對象在記憶體中存儲的布局可以分為3塊區域:對象頭(Header)、 執行個體資料(Instance Data)和對齊填充(Padding)。
在 JVM 中,Java對象儲存在堆中時,由以下三部分組成:
- 對象頭(object header):包括了關于堆對象的布局、類型、GC狀态、同步狀态和辨別哈希碼的基本資訊。Java對象和vm内部對象都有一個共同的對象頭格式。
- 執行個體資料(Instance Data):主要是存放類的資料資訊,父類的資訊,對象字段屬性資訊。
- 對齊填充(Padding):為了位元組對齊,填充的資料,不是必須的。湊齊8位元組的倍數。
2.1 對象頭
對象頭包括兩部分資訊,第一部分用于存儲對象自身的運作時資料(MarkWord), 如哈希碼(HashCode)、GC分代年齡、鎖狀态标志、線程持有的鎖、偏向線程ID、偏向時 間戳等。對象頭的另外一部分是類型指針(Klass Pointer),即對象指向它的類中繼資料的指針,虛拟機通過這個指針來确定這個對象是哪個類的執行個體。
// oopDesc hotspot對象頭
volatile markOop _mark;
union _metadata {
wideKlassOop _klass;
narrowOop _compressed_klass;
} _metadata;
Mark Word在32位JVM中的長度是32bit,在64位JVM中長度是64bit。
markword組成内容基本一緻。
- 鎖标志位(lock):區分鎖狀态,11時表示對象待GC回收狀态, 隻有最後2位鎖辨別(11)有效。
- biased_lock:是否偏向鎖,由于無鎖和偏向鎖的鎖辨別都是 01,沒辦法區分,這裡引入一位的偏向鎖辨別位。
- 分代年齡(age):表示對象被GC的次數,當該次數到達門檻值的時候,對象就會轉移到老年代。
- 對象的hashcode(hash):運作期間調用System.identityHashCode()來計算,延遲計算,并把結果指派到這裡。當對象加鎖後,計算的結果31位不夠表示,在偏向鎖,輕量鎖,重量鎖,hashcode會被轉移到Monitor中。
- 偏向鎖的線程ID(JavaThread):偏向模式的時候,當某個線程持有對象的時候,對象這裡就會被置為該線程的ID。 在後面的操作中,就無需再進行嘗試擷取鎖的動作。
- epoch:偏向鎖在CAS鎖操作過程中,偏向性辨別,表示對象更偏向哪個鎖。
- ptr_to_lock_record:輕量級鎖狀态下,指向棧中鎖記錄的指針。當鎖擷取是無競争的時,JVM使用原子操作而不是OS互斥。這種技術稱為輕量級鎖定。在輕量級鎖定的情況下,JVM通過CAS操作在對象的markword中設定指向鎖記錄的指針。
- ptr_to_heavyweight_monitor:重量級鎖狀态下,指向對象螢幕Monitor的指針。如果兩個不同的線程同時在同一個對象上競争,則必須将輕量級鎖定更新到Monitor以管理等待的線程。在重量級鎖定的情況下,JVM在對象的ptr_to_heavyweight_monitor設定指向Monitor的指針。
2.2 對象資訊
使用openjdk的jol工具列印對象資訊。引入依賴
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.8</version>
</dependency>
1 無屬性對象
// 列印無屬性對象
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
// 對象資訊
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
對象資訊分析
- OFFSET:偏移位址,機關位元組;
- SIZE:占用的記憶體大小,機關為位元組;
- TYPE DESCRIPTION:類型描述,其中object header為對象頭;
- VALUE:對應記憶體中目前存儲的值,二進制32位;
對于普通無屬性對象,一共占用16位元組,其中對象頭markword占用8個位元組,類型指針占用4個位元組,剩餘4個位元組用于對齊填充,沒有執行個體資料。
JDK8版本預設開啟指針壓縮(
-XX:+UseCompressedOops
),如果關閉指針壓縮,類型指針占用8個位元組,不再需要對齊填充。
2 數組對象
// 列印數組
System.out.println(ClassLayout.parseInstance(new int[]{}).toPrintable());
// 對象資訊
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) // 數組長度 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 0 int [I.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
對于數組對象,類型指針後有4個位元組記錄數組長度。因為虛拟機可以通過普通Java對象的中繼資料資訊确定Java對象大小,如果數組長度不确定,則無法推斷出數組對象大小。
3 有屬性對象
// 列印有屬性對象
System.out.println(ClassLayout.parseInstance(new User()).toPrintable());
class User {
int id; // 4B
String name; // 4B 未經類型壓縮為8B
byte b; // 1B
Object o; // 4B 未經類型壓縮為8B
}
// 對象資訊
com.lzp.java.jvm.memory.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
// 對象頭 12位元組
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 65 cc 00 f8 (01100101 11001100 00000000 11111000) (-134165403)
// 執行個體資料
12 4 int User.id 0
16 1 byte User.b 0
17 3 (alignment/padding gap) // 對齊
20 4 java.lang.String User.name null
24 4 java.lang.Object User.o null
// 對齊填充
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total
通過這一節的分析,我們可以比較輕松地估計出一個對象的大小,即對象頭+執行個體資料+對象填充,得到一個8位元組倍數的值。
2.3 其他
1 JVM每次GC,對象分代年齡加1,當年齡增加到15時晉升到老年代。為什麼是15?
在Mark Word中可以發現标記對象分代年齡的配置設定的空間是4bit,而4bit能表示的最大數就是2^4-1 = 15。
2 為什麼要進行指針壓縮?
通過指針壓縮,類型指針、對象引用等由8位元組轉為4個位元組。降低對象占用的記憶體大小,順便減輕GC壓力;當指針移動時,減少帶寬損耗。
版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。