由於Java面向對象的思想,在JVM中需要大量存儲對象,存儲時爲了實現一些額外的功能,需要在對象中添加一些標記字段用於增強對象功能,這些標記字段組成了對象頭。
1.對象頭形式
JVM中對象頭的方式有以下兩種(以32位JVM爲例):
1.1.普通對象
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
1.2.數組對象
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
2.對象頭的組成
2.1.Mark Word
這部分主要用來存儲對象自身的運行時數據,如hashcode、gc分代年齡等。mark word的位長度爲JVM的一個Word大小,也就是說32位JVM的Mark word爲32位,64位JVM爲64位。
爲了讓一個字大小存儲更多的信息,JVM將字的最低兩個位設置爲標記位,不同標記位下的Mark Word示意如下:
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|-------------------------------------------------------|--------------------|
其中各部分的含義如下:
lock:2位的鎖狀態標記位,由於希望用盡可能少的二進制位表示儘可能多的信息,所以設置了lock標記。該標記的值不同,整個mark word表示的含義不同。
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的指針。
64位下的標記字與32位的相似,不再贅述:
|------------------------------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | Normal |
|------------------------------------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | Biased |
|------------------------------------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
| | lock:2 | Marked for GC |
|------------------------------------------------------------------------------|--------------------|
2.2.class pointer
這一部分用於存儲對象的類型指針,該指針指向它的類元數據,JVM通過這個指針確定對象是哪個類的實例。該指針的位長度爲JVM的一個字大小,即32位的JVM爲32位,64位的JVM爲64位。
如果應用的對象過多,使用64位的指針將浪費大量內存,統計而言,64位的JVM將會比32位的JVM多耗費50%的內存。爲了節約內存可以使用選項+UseCompressedOops開啓指針壓縮,其中,oop即ordinary object pointer普通對象指針。開啓該選項後,下列指針將壓縮至32位:
每個Class的屬性指針(即靜態變量)
每個對象的屬性指針(即對象變量)
普通對象數組的每個元素指針
當然,也不是所有的指針都會壓縮,一些特殊類型的指針JVM不會優化,比如指向PermGen的Class對象指針(JDK8中指向元空間的Class對象指針)、本地變量、堆棧元素、入參、返回值和NULL指針等。
2.3.array length
如果對象是一個數組,那麼對象頭還需要有額外的空間用於存儲數組的長度,這部分數據的長度也隨着JVM架構的不同而不同:32位的JVM上,長度爲32位;64位JVM則爲64位。64位JVM如果開啓+UseCompressedOops選項,該區域長度也將由64位壓縮至32位。