1、Java 對象內存布局(HotSpot 虛擬機)
????????在 ?HotSpot 虛擬機? 中,一個 Java 對象在堆內存中的存儲布局可以分為以下幾個部分:
????????1、對象頭(Object Header)?
????????????????對象頭是對象內存布局中最重要的部分之一,它存儲了關于對象自身的運行時數據,如哈希碼、GC 分代年齡、鎖狀態等。??
????????????????對象頭又分為兩部分(在 64 位 JVM 中):
組成部分 | 說明 |
---|---|
?Mark Word(標記字段)?? | 存儲對象自身的運行時數據,如:哈希碼、GC 分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID等 |
?Klass Pointer(類型指針)?? | 指向對象所屬類的元數據(即 Class 對象)的指針,JVM 通過它確定該對象是哪個類的實例 |
注意:
? ? ? ? 1、對象頭是實現 synchronized、hashCode、垃圾回收、鎖升級等機制的基礎!
? ? ? ? 2、如果對象是一個 ?數組,對象頭中還會多一個部分:?數組長度(Array Length)??
????????1、Mark Word(標記字段)詳解(以 64 位 JVM 為例)
????????????????Mark Word 是對象頭的一部分,它是一個 ?動態的數據結構,根據對象當前的狀態(是否偏向鎖、輕量級鎖、重量級鎖等),?存儲的內容會動態變化。
鎖狀態 | 存儲內容簡述 |
---|---|
?無鎖狀態? | 對象的 hashCode、GC 分代年齡 |
?偏向鎖? | 偏向線程 ID、偏向時間戳、對象分代年齡 |
?輕量級鎖? | 指向棧中鎖記錄的指針 |
?重量級鎖? | 指向 Monitor(管程/互斥鎖)的指針 |
?GC 標記? | 與垃圾回收相關的標記(如三色標記) |
????????Mark Word 利用了 ?位運算與掩碼,在一個 64-bit 的長整型中存儲多種信息,非常精巧高效。
? ? ? ?
????????2、Klass Pointer(類型指針)
????????????????是一個指針,指向該對象所屬的 ?類元數據(即 .class 對象)?,JVM 通過這個指針判斷該對象是哪個類的實例。
????????????????在開啟壓縮指針(Compressed OOPs,默認開啟)的情況下,占 ?4 字節;未開啟則占 ?8 字節。
通過 Klass Pointer,JVM 知道這個對象是哪個 Class 的實例,從而可以找到方法、字段、父類等信息。
????????2、實例數據(Instance Data)?
????????????????這是對象真正存儲的 ?有效信息,也就是代碼中定義的各種類型的字段(成員變量)?,包括:
? ? ? ? ? ? ? ? 1、從父類繼承的字段
? ? ? ? ? ? ? ? 2、當前類中定義的字段
? ? ? ? ? ? ? ? 3、不同數據類型的字段(如 int、long、引用類型等)
????????實例數據部分是對象“業務數據”的存儲區域,它的排列順序受 JVM 實現與字段定義順序影響,也可能被優化(如字段重排序)。?
JVM 在布局時,可能會按照以下原則排列實例數據:
- ?相同大小的字段盡量放在一起?
- ?父類字段在前,子類字段在后?
- 可能進行 ?字段重排序以優化內存對齊
?????????3. ?對齊填充(Padding)?
?????????????????對齊填充不是必須的,但通常存在。
????????????????HotSpot 虛擬機要求 ?對象的大小必須是 8 字節的整數倍(即對象總大小對齊到 8 字節)?,這是為了提高內存訪問效率(CPU 讀取內存通常是按 8 字節對齊的)。
????????????????如果對象頭 + 實例數據的總大小不是 8 的倍數,JVM 會在對象末尾填充一些 ?無意義的空白字節(Padding)?,以達到對齊的目的。
對齊填充 ?不存儲任何有用信息,僅僅是為了內存布局優化。?
2、Java 對象內存布局總結(圖示)
????????下面是 ?一個普通 Java 對象在內存中的布局(64 位 JVM,開啟壓縮指針)的簡化圖示:?
+---------------------------+
| 對象頭 (Header) |
| - Mark Word (8字節) | --> 哈希、GC年齡、鎖狀態等
| - Klass Pointer (4字節) | --> 指向 Class 元數據
| | (如果是數組,還會有數組長度字段 4字節)
+---------------------------+
| 實例數據 (Fields) | --> 你定義的成員變量(int, 引用等)
+---------------------------+
| 對齊填充 (Padding) | --> 保證總大小是 8 字節倍數(可能沒有)
+---------------------------+
3、對象大小估算(示例)
????????估算一個 Java 對象在內存中占用的空間大小示例
public class User{int id; // 4 字節String name; // 4 字節(引用類型,壓縮指針下)boolean male; // 1 字節int age; // 4 字節
}
注意:這里說的是對象自身占用的內存,不包括它引用的對象(比如 name 是 String,String 對象在堆上另存)
????????對象布局分析(64 位 JVM,開啟壓縮指針):
成部分 | 大小(字節) | 說明 |
---|---|---|
?對象頭? | 12 字節 | Mark Word(8) + Klass Pointer(4) |
?實例數據? | 4 (int id) + 4 (引用 name) + 1 (boolean male) + 4 (int age) = 13 字節 | |
?對齊填充? | 3 字節 | 總計 12 + 13 = 25,不是 8 的倍數,填充到 28 字節(32 - 25 = 3) |
大約占用 28 字節(實際可能略有差異,依賴 JVM 實現)?
????????4、特殊對象:數組對象的內存布局
????????????????如果對象是一個 ?數組,比如?int[]
、String[]
,那么對象頭中會多一個字段:數組長度(Array Length,4 字節)?
+---------------------------+
| 對象頭 |
| - Mark Word (8字節) |
| - Klass Pointer (4字節) |
| - 數組長度 (4字節) | <-- 僅數組對象有
+---------------------------+
| 數組元素數據 | --> 比如 int[] 就是連續的 int 值
+---------------------------+
| 對齊填充 (如有) |
+---------------------------+
數組對象比普通對象多存儲一個長度信息,占用額外 4 字節(壓縮指針下)。
2、總結:
組成部分 | 說明 | 是否必有 | 大小(64位,壓縮指針) |
---|---|---|---|
?對象頭(Header)?? | 包括 Mark Word 和 Klass Pointer | ? 必有 | 通常 12 字節(8 + 4) |
?Mark Word? | 存儲哈希、GC 年齡、鎖狀態等動態信息 | ? 是對象頭的一部分 | 8 字節 |
?Klass Pointer? | 指向該對象的類元數據(Class) | ? 是對象頭的一部分 | 4 字節(可開啟/關閉壓縮) |
?數組長度(僅數組)?? | 數組對象才有,表示數組長度 | ? 僅數組對象 | 4 字節 |
?實例數據(Fields)?? | 對象的成員變量(包括繼承的字段) | ? 必有 | 依據字段類型而定 |
?對齊填充(Padding)?? | 保證對象總大小是 8 字節對齊 | ? 可能沒有 | 0~7 字節 |
3、補充:
主題 | 說明 |
---|---|
?synchronized 的實現? | 依賴對象頭中的 Mark Word 實現鎖狀態記錄 |
?hashCode() 的默認實現? | 默認與對象頭中的 Mark Word 相關(可重寫) |
?垃圾回收與對象年齡? | 對象頭中存儲 GC 分代年齡,用于判斷是否進入老年代 |
?鎖升級(偏向鎖、輕量級鎖、重量級鎖)?? | 基于對象頭 Mark Word 中的狀態標志實現 |
?對象內存大小查看工具? | 如 ?JOL(Java Object Layout)?,可以精確查看對象布局與大小 |
?????????Java 對象在內存中的布局分為對象頭(包括 Mark Word 和 Klass Pointer)、實例數據(成員變量)、對齊填充三部分,其中對象頭是實現鎖、GC、哈希等機制的核心,對象大小受字段類型、對齊規則等影響。