JVM——深入分析對象的內存布局


概述

一個對象本身的內在結構需要一種描述方式,這個描述信息是以字節碼的方法存儲在方法區中的。
Class 本身就是一個對象,都以 KB 為單位,如果 new Integer() 為了表示一個數據就占用KB級別的內存就有點不值了,下面講解 JVM 是如何做的。
為了表示對象的屬性、方法等信息,不得不需要結構描述。Hotspot VM 使用對象頭部的一個指針指向 Class 區域的方式來找到對象的 Class 描述,以及內部的方法、屬性入口。如下圖所示:

在 HotSpot 虛擬機中,對象在內存中存儲布局分為 3 塊區域:對象頭(Header)、實例數據(Instance Data)、對齊填充(Padding),下面詳細講解各部分內容。

對象頭(Header)

HotSpot 虛擬機的對象頭包括兩部分(非數組對象)信息,如下圖所示:

  • 第一部分用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC 分代年齡、鎖狀態標志、線程持有的鎖、偏向線程 ID、偏向時間戳、對象分代年齡,這部分信息稱為“Mark Word”;Mark Word 被設計成一個非固定的數據結構以便在極小的空間內存儲盡量多的信息,它會根據自己的狀態復用自己的存儲空間。
  • 第二部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例;
  • 如果對象是一個 Java 數組,那在對象頭中還必須有一塊用於記錄數組長度的數據。因為虛擬機可以通過普通 Java 對象的元數據信息確定 Java 對象的大小,但是從數組的元數據中無法確定數組的大小。

這部分數據的長度在 32 位和 64 位的虛擬機(未開啟壓縮指針)中分別為 32bit 和 64bit。

例如,在 32 位的 HotSpot 虛擬機中,如果對象處於未被鎖定的狀態下,那么 Mark Word 的 32bit 空間中的 25bit 用於存儲對象哈希碼,4bit 用於存儲對象分代年齡,2bit 用於存儲鎖標志位,1bit 固定為 0,如下表所示:

在 32 位系統下,存放 Class 指針的空間大小是 4 字節,Mark Word 空間大小也是4字節,因此頭部就是 8 字節,如果是數組就需要再加 4 字節表示數組的長度,如下表所示:

在 64 位系統及 64 位 JVM 下,開啟指針壓縮,那么頭部存放 Class 指針的空間大小還是4字節,而 Mark Word 區域會變大,變成 8 字節,也就是頭部最少為 12 字節,如下表所示:

壓縮指針:開啟指針壓縮使用算法開銷帶來內存節約,Java 對象都是以 8 字節對齊的,也就是以 8 字節為內存訪問的基本單元,那么在地理處理上,就有 3 個位是空閑的,這 3 個位可以用來虛擬,利用 32 位的地址指針原本最多只能尋址 4GB,但是加上 3 個位的 8 種內部運算,就可以變化出 32GB 的尋址。

實例數據(Instance Data) 

實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容。

這部分的存儲順序會受到虛擬機分配策略參數(FieldsAllocationStyle)和字段在 Java 源碼中定義順序的影響。

對齊填充(Padding)

對齊填充不是必然存在的,沒有特別的含義,它僅起到占位符的作用。

由於 HotSpot VM 的自動內存管理系統要求對象起始地址必須是 8 字節的整數倍,也就是說對象的大小必須是 8 字節的整數倍。對象頭部分是 8 字節的倍數,所以當對象實例數據部分沒有對齊時,就需要通過對齊填充來補全。

估算對象大小

32 位系統下,當使用 new Object() 時,JVM 將會分配 8(Mark Word+類型指針) 字節的空間,128 個 Object 對象將占用 1KB 的空間。
如果是 new Integer(),那么對象里還有一個 int 值,其占用 4 字節,這個對象也就是 8+4=12 字節,對齊后,該對象就是 16 字節。

以上只是一些簡單的對象,那么對象的內部屬性是怎么排布的?

Class A {
    int i;
    byte b;
    String str;
}

其中對象頭部占用 ‘Mark Word’4 + ‘類型指針’4 = 8 字節;byte 8 位長,占用 1 字節;int 32 位長,占用 4 字節;String 只有引用,占用 4 字節;
那么對象 A 一共占用了 8+1+4+4=17 字節,按照 8 字節對齊原則,對象大小也就是 24 字節。

這個計算看起來是沒有問題的,對象的大小也確實是 24 字節,但是對齊(padding)的位置並不對:

在 HotSpot VM 中,對象排布時,間隙是在 4 字節基礎上的(在 32 位和 64 位壓縮模式下),上述例子中,int 后面的 byte,空隙只剩下 3 字節,接下來的 String 對象引用需要 4 字節來存放,因此 byte 和對象引用之間就會有 3 字節對齊,對象引用排布后,最后會有 4 字節對齊,因此結果上依然是 7 字節對齊。此時對象的結構示意圖,如下圖所示:

參考資料

[1] Java特種兵, 3.5 - 淺析Java對象的內存結構

[2] Java並發編程的藝術, 2.2.1 - Java對象頭

[3] 深入理解Java虛擬機, 2.3.2 - 對象的內存布局


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM