總結(jié)圖
Java 內(nèi)存結(jié)構(gòu)
第一部分:對象頭
-
markword:用于存儲對象自身的運(yùn)行時數(shù)據(jù)玄货,如哈希碼行嗤、GC分代年齡、鎖狀態(tài)標(biāo)志嘿架、線程持有的鎖等瓶珊。這部分?jǐn)?shù)據(jù)長度在32位機(jī)器和64位機(jī)器虛擬機(jī)中分別為4字節(jié)和8字節(jié)(64位的JVM為了節(jié)約內(nèi)存可以使用選項+UseCompressedOops開啟指針壓縮,開啟該選項后耸彪,占用字節(jié)數(shù)降為4字節(jié))伞芹;
image 類型指針:即對象指向它的類元數(shù)據(jù)(保存在方法區(qū))的指針,虛擬機(jī)通過這個指針來確定這個對象屬于哪個類的實例蝉娜,指針占用4個字節(jié)(64位機(jī)器占8個字節(jié))唱较;
數(shù)組長度(只有數(shù)組對象才有):如果是 Java 數(shù)組,對象頭必須有一塊用于記錄數(shù)組長度的數(shù)據(jù)召川,用4個字節(jié)int來記錄數(shù)組長度南缓;
第二部分:實例數(shù)據(jù)
實例數(shù)據(jù)是對象真正存儲的有效信息,也是程序代碼中定義的各種類型的字段內(nèi)容荧呐。無論是從父類繼承下來還是在子類中定義
的數(shù)據(jù)汉形,都需要記錄下來。
原生類型的內(nèi)存占用情況如下:
- boolean 1
- byte 1
- short 2
- char 2
- int 4
- float 4
- long 8
- double 8
引用類型的內(nèi)存占用和系統(tǒng)位數(shù)以及啟動參數(shù)UseCompressedOops有關(guān)
- 32位系統(tǒng)占4字節(jié)
- 64位系統(tǒng)倍阐,開啟 UseCompressedOops時概疆,占用4字節(jié),否則是8字節(jié)
第三部分:對齊填充
對于hotspot迅疾的自動內(nèi)存管理系統(tǒng)要求對象的起始地址必須為8字節(jié)的整數(shù)倍峰搪,這就要求當(dāng)部位8字節(jié)的整數(shù)倍時岔冀,就需要填充數(shù)據(jù)對其填充。原因是訪問未對齊的內(nèi)存概耻,處理器需要做兩次內(nèi)存訪問使套,而對齊的內(nèi)存訪問僅需一次訪問罐呼。
如何計算或者獲取某個 Java 對象的大小?
32 位系統(tǒng)下,當(dāng)使用 new Object() 時童漩,JVM 將會分配 8(Mark Word+類型指針) 字節(jié)的空間弄贿,128 個 Object 對象將占用 1KB 的空間。
如果是 new Integer()矫膨,那么對象里還有一個 int 值差凹,其占用 4 字節(jié),這個對象也就是 8+4=12 字節(jié)侧馅,對齊后危尿,該對象就是 16 字節(jié)。
以上只是一些簡單的對象馁痴,那么對象的內(nèi)部屬性是怎么排布的谊娇?
Class A {
int i;
byte b;
String str;
}
其中對象頭部占用 「Mark Word」4 + 「類型指針」4 = 8 字節(jié);byte 8 位長罗晕,占用 1 字節(jié)济欢;int 32 位長,占用 4 字節(jié)小渊;String 只有引用法褥,占用 4 字節(jié);
那么對象 A 一共占用了 8+1+4+4=17 字節(jié)酬屉,按照 8 字節(jié)對齊原則半等,對象大小也就是 24 字節(jié)。
這個計算看起來是沒有問題的呐萨,對象的大小也確實是 24 字節(jié)杀饵,但是對齊(padding)的位置并不對:
在 HotSpot VM 中,對象排布時谬擦,間隙是在 4 字節(jié)基礎(chǔ)上的(在 32 位和 64 位壓縮模式下)切距,上述例子中,int 后面的 byte惨远,空隙只剩下 3 字節(jié)蔚舀,接下來的 String 對象引用需要 4 字節(jié)來存放,因此 byte 和對象引用之間就會有 3 字節(jié)對齊锨络,對象引用排布后,最后會有 4 字節(jié)對齊狼牺,因此結(jié)果上依然是 7 字節(jié)對齊羡儿。此時對象的結(jié)構(gòu)示意圖,如下圖所示:
代碼驗證
public class Main {
public static void main(String[] args) throws Exception {
final List<AAAAA> aaa = new ArrayList<>(100000);
final List<BBBBB> bbb = new ArrayList<>(100000);
final List<CCCCC> ccc = new ArrayList<>(100000);
final List<DDDDD> ddd = new ArrayList<>(100000);
for (int i = 0; i < 100000; i++) {
aaa.add(new AAAAA());
bbb.add(new BBBBB());
ccc.add(new CCCCC());
ddd.add(new DDDDD());
}
System.in.read();
}
}
class AAAAA {
}
class BBBBB {
int a = 1;
}
class CCCCC {
long a = 1L;
}
class DDDDD {
String s = "hello";
}
本地的執(zhí)行環(huán)境是64位的JDK8是钥,且使用默認(rèn)的啟動參數(shù)掠归,運(yùn)行之后通過 jmap-dump命令生成dump文件缅叠,用MAT打開
- A對象只包含一個對象頭,大小占12字節(jié)(markword 4 字節(jié)虏冻,類型指針 8 字節(jié))肤粱,不是8的倍數(shù),需要4字節(jié)進(jìn)行填充厨相,一共占16字節(jié)
- B對象包含一個對象頭和int類型领曼,12+4=16,正好是8的倍數(shù)蛮穿,不需要填充庶骄,占16字節(jié)。
- C對象包含一個對象頭和long類型践磅,12+8=20单刁,不是8的倍數(shù),需要4個字節(jié)進(jìn)行填充府适,占24字節(jié)
- D對象包含一個對象頭和引用類型羔飞,12+4=16,正好是8的倍數(shù)檐春,不需要填充逻淌,占16字節(jié)。
因為 UseCompressedOops 在 JDK8 是是默認(rèn)開啟的喇聊,為了驗證不開啟時的內(nèi)存占用情況恍风,可以添加參數(shù) -XX:-UseCompressedOops 主動關(guān)閉指針壓縮,結(jié)果如下
?
- A對象只包含一個對象頭誓篱,大小占16字節(jié)(markword 8 字節(jié)朋贬,類型指針 8 字節(jié)),一共占16字節(jié)
- B對象包含一個對象頭和int類型窜骄,16+4=20锦募,填充后占 24 字節(jié)。
- C對象包含一個對象頭和long類型邻遏,16+8=24糠亩,占24字節(jié)
- D對象包含一個對象頭和引用類型,16+8=24准验,占 24 字節(jié)赎线。
參考資料
https://www.cnblogs.com/magialmoon/p/3757767.html
https://mp.weixin.qq.com/s/BfWMp-3vPcg1eMgL4D249g