JVM系列之:詳解java object物件在heap中的結構
阿新 • • 發佈:2020-07-20
[toc]
# 簡介
在之前的文章中,我們介紹了使用JOL這一神器來解析java類或者java例項在記憶體中佔用的空間地址。
今天,我們會更進一步,剖析一下在之前文章中沒有講解到的更深層次的細節。一起來看看吧。
# 物件和其隱藏的祕密
java.lang.Object大家應該都很熟悉了,Object是java中一切物件的鼻祖。
接下來我們來對這個java物件的鼻祖進行一個詳細的解剖分析,從而理解JVM的深層次的祕密。
工具當然是使用JOL:
~~~java
@Slf4j
public class JolUsage {
@Test
public void useJol(){
log.info("{}", VM.current().details());
log.info("{}", ClassLayout.parseClass(Object.class).toPrintable());
log.info("{}", ClassLayout.parseInstance(new Object()).toPrintable());
}
}
~~~
程式碼很簡單,我們列印JVM的資訊,Object class和一個新的Object例項的資訊。
看下輸出:
~~~java
[main] INFO com.flydean.JolUsage - # Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
10:27:32.311 [main] INFO com.flydean.JolUsage - java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
10:27:32.312 [main] INFO com.flydean.JolUsage - java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 86 06 00 00 (10000110 00000110 00000000 00000000) (1670)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
~~~
從上面的結果我們知道,在64位的JVM中,一個Object例項是佔用16個位元組。
因為Object物件中並沒有其他物件的引用,所以我們看到Object物件只有一個12位元組的物件頭。剩下的4個位元組是填充位。
# Object物件頭
那麼這12位元組的物件頭是做什麼用的呢?
如果想要深入瞭解這12位元組的物件頭,當然是要去研讀一下JVM的原始碼:src/share/vm/oops/markOop.hpp。
有興趣的小夥伴可以去看看。如果沒有興趣,沒關係,這裡給大家一個張總結的圖:
![](https://img-blog.csdnimg.cn/20200618121615778.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70)
javaObject物件的物件頭大小根據你使用的是32位還是64位的虛擬機器的不同,稍有變化。這裡我們使用的是64位的虛擬機器為例。
Object的物件頭,分為兩部分,第一部分是Mark Word,用來儲存物件的執行時資料比如:hashcode,GC分代年齡,鎖狀態,持有鎖資訊,偏向鎖的thread ID等等。
在64位的虛擬機器中,Mark Word是64bits,如果是在32位的虛擬機器中Mark Word是32bits。
第二部分就是Klass Word,Klass Word是一個型別指標,指向class的元資料,JVM通過Klass Word來判斷該物件是哪個class的例項。
且慢!
有的小夥伴可能發現了問題,之前我們用JOL解析Object物件的時候,Object head大小是12位元組,也就是96bits,這裡怎麼寫的是128bits?
![](https://img-blog.csdnimg.cn/20200618122419596.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70)
沒錯,如果沒有開啟COOPs就是128bits,如果開啟了COOPs,那麼Klass Word的大小就從64bits降到了32bits。
還記得我們之前講的COOPs嗎?
COOPs就是壓縮物件指標技術。
物件指標用來指向一個物件,表示對該物件的引用。通常來說在64位機子上面,一個指標佔用64位,也就是8個位元組。而在32位機子上面,一個指標佔用32位,也就是4個位元組。
實時上,在應用程式中,這種物件的指標是非常非常多的,從而導致如果同樣一個程式,在32位機子上面執行和在64位機子上面執行佔用的記憶體是完全不同的。64位機子記憶體使用可能是32位機子的1.5倍。
而壓縮物件指標,就是指把64位的指標壓縮到32位。
怎麼壓縮呢?64位機子的物件地址仍然是64位的。壓縮過的32位存的只是相對於heap base address的位移。
我們使用64位的heap base地址+ 32位的地址位移量,就得到了實際的64位heap地址。
物件指標壓縮在Java SE 6u23 預設開啟。在此之前,可以使用-XX:+UseCompressedOops來開啟。
# 陣列物件頭
java中有一個非常特別的物件叫做陣列,陣列的物件頭和Object有什麼區別嗎?
我們用JOL再看一次:
~~~java
log.info("{}",ClassLayout.parseClass(byte[].class).toPrintable());
log.info("{}",ClassLayout.parseInstance("www.flydean.com".getBytes()).toPrintable());
~~~
上面的例子中我們分別解析了byte陣列的class和byte陣列的例項:
~~~java
10:27:32.396 [main] INFO com.flydean.JolUsage - [B object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 16 (object header) N/A
16 0 byte [B. N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
10:27:32.404 [main] INFO com.flydean.JolUsage - [B 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) 22 13 07 00 (00100010 00010011 00000111 00000000) (463650)
12 4 (object header) 0f 00 00 00 (00001111 00000000 00000000 00000000) (15)
16 15 byte [B. N/A
31 1 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 1 bytes external = 1 bytes total
~~~
看到區別了嗎?我們發現數組的物件頭是16位元組,比普通物件的物件頭多出了4個位元組。這4個位元組就是陣列的長度。
# 整個物件的結構
好了,寫到這裡我們來總結一下,java物件的結構可以分為普通java物件和陣列物件兩種:
![](https://img-blog.csdnimg.cn/20200618135903311.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70)
陣列物件在物件頭中多了一個4位元組的長度欄位。
大家看到最後的位元組是padding填充位元組,為什麼要填充呢?
因為JVM是以8位元組為單位進行對其的,如果不是8位元組的整數倍,則需要補全。
> 本文連結:[http://www.flydean.com/jvm-java-object-in-heap/](http://www.flydean.com/jvm-java-object-in-heap/)
>
> 最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
>
> 歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!
![](https://img-blog.csdnimg.cn/20200709152618916.png)