為什么要學習Java對象頭
學習Java對象頭主要是為了解synchronized底層原理,synchronized鎖升級過程,Java並發編程等。
JAVA對象頭
由於Java面向對象的思想,在JVM中需要大量存儲對象,存儲時為了實現一些額外的功能,需要在對象中添加一些標記字段用於增強對象功能,這些標記字段組成了對象頭。
在HotSpot虛擬機中,對象在內存中存儲的布局可以分為3塊區域:對象頭(Header),實例數據(Instance Data)和對齊填充(Padding)。
也就是說 JAVA對象 = 對象頭 + 實例數據 + 對象填充。
其中,對象頭由兩部分組成,一部分用於存儲自身的運行時數據,稱之為 Mark Word,另外一部分是類型指針,及對象指向它的類元數據的指針。
對象頭 = Mark Word + 類型指針
(未開啟指針壓縮的情況下)
在32位系統中,Mark Word = 4 bytes = 32 bits,對象頭 = 8 bytes = 64 bits;
在64位系統中,Mark Word = 8 bytes = 64 bits ,對象頭 = 16 bytes = 128bits;
bytes 是字節,bits 是位。所以說,在32位JVM虛擬機系統中,Mark Word部分,占了4個字節,Klass Word部分也占了4個字節,所以,對象頭大小為8個字節。在64位JVM虛擬機系統中,Mark Word部分,占了8個字節,Klass Word部分也占了8個字節,所以,對象頭大小為16個字節。
32位虛擬機對象頭
32位虛擬機普通對象的對象頭
|-----------------------------------------------------------|
| Object Header (64 bits) |
|---------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|---------------------------------|-------------------------|
32位虛擬機數組對象的對象頭
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
32位虛擬機對象頭詳情如下
|--------------------------------------------------------------------------------------------------------------|
| Object Header(64bits) |
|--------------------------------------------------------------------------------------------------------------|
| Mark Word(32bits) | Klass Word(32bits) | State |
|--------------------------------------------------------------------------------------------------------------|
| hashcode:25 | age:4 | biased_lock:0 | 01 | OOP to metadata object | Nomal |
|--------------------------------------------------------------------------------------------------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | 01 | OOP to metadata object | Biased |
|--------------------------------------------------------------------------------------------------------------|
| ptr_to_lock_record:30 | 00 | OOP to metadata object | Lightweight Locked |
|--------------------------------------------------------------------------------------------------------------|
| ptr_to_heavyweight_monitor:30 | 10 | OOP to metadata object | Heavyweight Locked |
|--------------------------------------------------------------------------------------------------------------|
| | 11 | OOP to metadata object | Marked for GC |
|--------------------------------------------------------------------------------------------------------------|
64位虛擬機對象頭
|--------------------------------------------------------------------------------------------------------------|
| Object Header(128bits) |
|--------------------------------------------------------------------------------------------------------------|
| Mark Word(64bits) | Klass Word(64bits) | State |
|--------------------------------------------------------------------------------------------------------------|
| unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:0| 01 | OOP to metadata object | Nomal |
|--------------------------------------------------------------------------------------------------------------|
| thread:54| epoch:2 |unused:1|age:4|biase_lock:1| 01 | OOP to metadata object | Biased |
|--------------------------------------------------------------------------------------------------------------|
| ptr_to_lock_record:62 | 00 | OOP to metadata object | Lightweight Locked |
|--------------------------------------------------------------------------------------------------------------|
| ptr_to_heavyweight_monitor:62 | 10 | OOP to metadata object | Heavyweight Locked |
|--------------------------------------------------------------------------------------------------------------|
| | 11 | OOP to metadata object | Marked for GC |
|--------------------------------------------------------------------------------------------------------------|
lock:2位的鎖狀態標記位,由於希望用盡可能少的二進制位表示盡可能多的信息,所以設置了lock標記。該標記的值不同,整個mark word表示的含義不同。
biased_lock | lock | 狀態 |
---|---|---|
0 | 01 | 無鎖 |
1 | 01 | 偏向鎖 |
0 | 00 | 輕量級鎖 |
0 | 10 | 重量級鎖 |
0 | 11 | GC標記 |
biased_lock:對象是否啟用偏向鎖標記,只占1個二進制位。為1時表示對象啟用偏向鎖,為0時表示對象沒有偏向鎖。
age:4位的Java對象年齡。在GC中,如果對象在Survivor區復制一次,年齡增加1。當對象達到設定的閾值時,將會晉升到老年代。默認情況下,並行GC的年齡閾值為15,並發GC的年齡閾值為6。由於age只有4位,所以最大值為15,這就是-XX:MaxTenuringThreshold
選項最大值為15的原因。
identity_hashcode:25位的對象標識Hash碼,采用延遲加載技術。調用方法System.identityHashCode()
計算,並會將結果寫到該對象頭中。當對象被鎖定時,該值會移動到管程Monitor中。
thread:持有偏向鎖的線程ID。
epoch:偏向時間戳。
ptr_to_lock_record:指向棧中鎖記錄的指針。
ptr_to_heavyweight_monitor:指向管程Monitor的指針。
如何查看對象頭
查看對象頭,就需要用到借助JOL工具。
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;
public class TestBiased {
public static void main(String[] args) {
Dog d= new Dog();
//打印JVM的詳細信息
out.println(VM.current().details());
//打印對應的對象頭信息
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
class Dog {
}
打印,大概如下。
21:46:08.204 c.TestBiased [main] - # Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
21:46:08.215 TestBiased [main] - cn.itcast.test.Dog 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) 50 c8 e1 3f (01010000 11001000 11100001 00111111) (1071761488)
12 4 (object header) 71 01 00 00 (01110001 00000001 00000000 00000000) (369)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
如果打印不一樣,可以加VM options參數-XX:-UseCompressedOops
,關閉指針壓縮。
要看懂VALUE這串數還得了解一下小端存儲
一個對象創建時:
如果開啟了偏向鎖(默認開啟),那么對象創建后,markword 值為 0x05 即最后 3 位為 101,這時它的
thread、epoch、age 都為 0
偏向鎖是默認是延遲的,不會在程序啟動時立即生效,如果想避免延遲,可以加 VM 參數
-XX:BiasedLockingStartupDelay=0
來禁用延遲
如果沒有開啟偏向鎖,那么對象創建后,markword 值為 0x01 即最后 3 位為 001,這時它的 hashcode、
age 都為 0,第一次用到 hashcode 時才會賦值
參考
memory-efficient-java
深入理解Java的對象頭mark word
Java對象頭詳解
java校招面試
Java對象結構與鎖實現原理及MarkWord詳解
干掉面試官1-synchronized底層原理(從Java對象頭說到即時編譯優化)
探究java對象頭——大端存儲與小端存儲