歡迎訪問我的主頁: https://heeheeaii.github.io/
在Java虛擬機(JVM)中,一個對象在內存中的存儲布局可以分為三個部分:對象頭(Object Header)、實例數據(Instance Data)和對齊填充(Padding)。理解這三個部分對于分析內存占用和性能優化非常重要。
- 對象頭(Object Header)
每個Java對象都必須有一個對象頭,這部分就像是對象的“身份證”,它存儲著與對象運行時相關的重要元數據。對象頭主要包含兩個部分:
Mark Word(標記字段):這部分是對象頭的核心,用于存儲對象的運行時信息,比如哈希碼、GC分代年齡、鎖狀態標志、偏向鎖ID等。這部分的大小在32位和64位JVM中是不同的,通常在64位JVM中占用8字節。
Klass Pointer(類指針):這是一個指針,指向該對象對應的類元數據(在方法區中)。通過這個指針,JVM可以確定這個對象是哪個類的實例。在64位JVM上,這個指針通常是8字節。
為了節省內存,現代64位JVM通常會開啟**壓縮指針(Compressed Oops)**技術。當開啟此技術后,原本8字節的類指針會被壓縮成4字節,這大大減少了每個對象占用的內存空間。
因此,在64位JVM上:
開啟壓縮指針(默認):對象頭大小為 12字節(8字節Mark Word + 4字節Klass Pointer)。
關閉壓縮指針:對象頭大小為 16字節(8字節Mark Word + 8字節Klass Pointer)。
- 實例數據(Instance Data)
這部分是真正存儲對象中所有實例字段(即成員變量)的地方,包括從父類繼承的字段。JVM會按照一定的順序來存放這些字段,通常的順序是:
父類中定義的變量。
當前類中定義的變量。
在字段的存儲順序上,JVM為了更高效地讀取,會對字段進行重新排序。一般來說,它會把占用空間小的字段(如byte、boolean、char、short)排列在占用空間大的字段(如long、double、Object引用)之后,以減少對齊填充帶來的內存浪費。
- 對齊填充(Padding)
由于JVM要求對象的總大小必須是8字節的倍數,以便于CPU高效地進行內存存取,所以當對象頭和實例數據加起來的總大小不是8的倍數時,JVM會在最后添加一些字節,這就是對齊填充。
這個過程可以理解為:總大小 = 對齊填充前的總大小 + 填充字節,最終的結果是8的倍數。
舉例說明
假設我們在64位JVM上,開啟了壓縮指針(默認設置),有一個Student類:
Java
class Student {
String name; // 引用類型
int age; // 基本類型
boolean isMale; // 基本類型
}
對象頭:12字節(開啟壓縮指針)。
實例數據:
name(引用類型):4字節(壓縮指針)。
age(int):4字節。
isMale(boolean):1字節。
實例數據總大小:4 + 4 + 1 = 9字節。
對齊填充:
當前總大小:12(對象頭) + 9(實例數據) = 21字節。
21不是8的倍數,下一個8的倍數是24。
所以需要填充 24 - 21 = 3字節。
最終,這個Student對象的總大小就是 24字節。