對象的內存佈局
在HotSpot虛擬機中,對象的內存佈局分爲以下3塊區域:
-
對象頭(報頭)
-
實例數據(實例數據)
-
對齊填充(填充)
對象頭
對象頭記錄了對象在運行過程中所需要使用的一些數據:
-
哈希碼
-
GC分代年齡
-
鎖狀態標誌
-
線程持有的鎖
-
偏向線程ID
-
偏向時間戳
對象頭可能包含類型指針,通過該指針能確定對象屬於哪個類。如果對象是一個數組,那麼對象頭還會包括數組長度。
實例數據
實例數據部分就是成員變量的值,其中包括父類成員變量和本類成員變量。
對齊填充
用於確保對象的總長度爲8字節的整數倍。
HotSpot VM的自動內存管理系統要求對象的大小必須是8字節的整數倍。而對象頭部分正好是8字節的倍數(1倍或2倍),因此,當對象實例數據部分沒有對齊時,就需要通過對齊填充來補全。
對齊填充並不是必然存在,也沒有特別的含義,它僅僅起着佔位符的作用。
對象的創建過程
類加載檢查
虛擬機在解析.
class
文件時,若遇到一條新指令,首先它會去檢查常量池中是否有這個類的符號引用,並且檢查這個符號引用所代表的類是否已被加載,解析和初始化過。如果沒有,那麼必須先執行相應的類加載過程。
爲新生對象分配內存
。對象所需內存的大小在類加載完成後便可完全確定,接下來從堆中劃分一塊對應大小的內存空間給新的對象分配堆中內存有兩種方式:
-
指針碰撞
如果Java 堆中內存絕對規整(說明採用的是“ 複製算法 ”或“ 標記整理法 ”),空閒內存和已使用內存中間放着一個指針作爲分界點指示器,那麼分配內存時只需需把指針空閒內存挪動一段與對象大小一樣的距離,這種分配方式稱爲“ 指針碰撞 ”。
-
空閒列表
如果Java的堆中內存並不規整,已使用的內存和空閒內存交錯(採用說明的英文的標記-清除法,有碎片),此時沒法簡單進行指針碰撞,VM必須維護一個列表,記錄其中哪些內存塊空閒可用。分配之時從空閒列表中找到一塊足夠大的內存空間劃分給對象實例。這種方式稱爲“ 空閒列表 ”。
初始化
分配完內存後,爲對象中的成員變量賦上初始值,設置對象頭信息,調用對象的構造函數方法進行初始化。
至此,整個對象的創建過程就完成了。
對象的訪問方式
所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配的。也就是說在建立一個對象時兩個地方都分配內存,在堆中分配的內存實際建立這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。那麼根據引用存放的地址類型的不同,對象有不同的訪問方式。
句柄訪問方式
堆中需要有一塊叫做“句柄池”的內存空間,句柄中包含了對象實例數據與類型數據各自的具體地址信息。
引用類型的變量存放的是該對象的句柄地址(參考)。訪問對象時,首先需要通過引用類型的變量找到該對象的句柄,然後根據句柄中對象的地址找到對象。
直接指針訪問方式
引用類型的變量直接存放對象的地址,從而不需要句柄池,通過引用能夠直接訪問對象。但對象所在的內存空間需要額外的策略存儲對象所屬的類信息的地址。
需要說明的是,熱點採用第二種方式,即直接指針方式來訪問對象,只需要一次尋址操作,所以在性能上比句柄訪問方式快一倍。但像上面所說,需要它額外的策略來存儲對象在方法區中類信息的地址。