對象的內存布局
在 HotSpot虛擬機中,對象在內存中存儲的布局分為三塊區域:對象頭,實例數據,和對齊填充。
對象頭
對象頭包括如下兩部分信息:
-
MarkWord:用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等。為了在極小空間內存儲更多的信息,它被設計成了一個非固定的數據結構,根據對象的狀態來復用自己的存儲空間,如下:
存儲內容 標志位 狀態 對象哈希碼、分代年齡 01 未鎖定 指向鎖記錄的指針 00 輕量級鎖定 指向重量級鎖的指針 10 膨脹(重量級鎖定) 空 11 GC標記 偏向線程ID\偏向時間戳、分代年齡 01 可偏向 -
類型指針:到對象類型數據的指針,即虛擬機通過這個指針來確定這個對象屬於哪個類。(有的虛擬機通過句柄池來實現)
-
如果對象是一個數組:對象頭還需要有一塊空間來記錄數組長度,因為對象可以通過類型指針判斷Java對象大小,而數組不行。
實例數據
是對象真正的有效數據,也就是代碼中所定義的各種類型的字段內容,無論是從父類繼承還是子類記錄的都必須進行存儲。
對齊填充
對齊填充並不是必然存在的,也沒有其它的意義,僅僅是占位符的作用,因為HotSpot虛擬機的自動內存管理系統要求對象地址必須是8的整數倍,當實例數據沒有對齊時,就需要對齊填充來進行補齊。
對象的訪問
當我們使用對象時,我們需要通過虛擬機棧上的reference數據(即worker)來操作堆上的具體對象。
public Worker buildWorker(){
Worker woker = new Woker();
worker.setAge(21);
....
return worker;
}
訪問具體對象的方式不同虛擬機有不同的實現,主流的方式有以下兩種
使用句柄池
在Java堆中專門划分處一部分內存作句柄池,reference中存儲的是對應對象的句柄地址,而句柄池中包含了對象實例數據和類型數據具體的地址信息,如下圖:
使用直接指針訪問
直接指針訪問,reference中直接存儲對象地址。
兩種方式的比較
- 使用句柄池來訪問最大的好處就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時整體空間位置)時只會改變句柄中的實例數據指針,而reference不需要任何改變。
- 使用直接指針訪問最大的好處就是快,節省了一次指針定位的時間開銷,由於對象訪問在java中非常頻繁,積少成多,節省這樣的開銷效益非常可觀。
- 主要虛擬機HotSpot采用直接指針訪問,但是許多其他語言和框架使用句柄這種思想也非常常見。