1.背景與大綱
在我們了解了java虛擬機的運行時數據區后,我們大概知道了虛擬機內存的概況,但是我們還是不清楚具體怎么存放的訪問的;
接下來,我們將深入探討HotSport虛擬機在java堆中對象的分配、布局、訪問的全過程。
2.對象創建

1.類加載:當遇到new指令時,先判斷這個類是否被加載、解析、初始化過,如果沒有,先執行相應類的加載過程(后面會詳細分析這個過程)。
2.分配內存:
如果Java堆內存是規整連續的,采用“指針碰撞”的分配方式,

如果是不連續規整的,采用“空閑列表”分配方式。如下圖:灰色表示已使用,數字表示可用

內存是否規整取決於垃圾收集器是否帶有壓縮整理功能。
Serial,ParNew等帶有Compact過程的收集器,采用的分配算法是“指針碰撞”。
而CMS這種基於Mark-Sweep算法的收集器,通常采用“空閑列表”分配方式。
線程安全問題:即便是修改指針指向位置,A\B兩個線程有可能會指向同一個地址

解決方案:
a.同步鎖:
b.TLAB:本地線程分配緩沖,把內存的分配動作按照線程划分在不同的空間進行
等TLAB用完分配新的TLAB時,才需要同步鎖
虛擬機是否使用TLAB,可以通過-XX:+/UseTLAB參數設定

3.對象初始化為零對象:
a.如果使用的是TLAB,這一步可以提前到TLAB分配的時候進行
b.作用:保證了實例字段在java代碼中可以不賦初始值就可以直接使用
4.對象頭進行設置,
包括這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。
5.java程序初始化,最后執行init方法,把對象按照程序員的意願進行初始化。這樣一個真正可用的對象才算完全生產出來。
3.對象內存布局

對象內存布局分為三塊區域
3.1.對象頭(Header)
對象頭主要包括:運行時數據、類型指針
3.1.1.運行時數據
對象頭,存儲對象自身的運行時數據,如哈希碼、對象的GC分代年齡、鎖狀態標志、偏向線程ID、偏向時間戳,
1.這部分數據的長度在32位和64位虛擬機中,分別為32bit和64bit。
2.官方稱其為:Mark Word
3.存儲的運行時數據過多(超出32bit、64bit)
4.Mark Word被設計成為一個非固定數據結構,以便在極小的空間內存存儲盡量多的信息

5.根據對象的狀態復用自己的存儲空間
3.1.2.類型指針
另一個部分是類型指針
1.定義:對象指向它的類元數據的指針
2.作用:虛擬機通過這個指針來確定這個對象是哪個類的實例
3.但是:並不是所有的虛擬機實現都必須在對象上保留類型指針
4.結論:查找對象的元數據信息並不一定要經過對象本身(下一節詳細講)
3.1.3.特例
另外,如果對象是java數組
1.對象頭必須有一塊用於記錄數組長度的數據
2.原因:普通java對象的元數據信息可以確定java對象的大小
3.但是,從數組的元數據中卻無法確定數組的大小
3.2.實例數據(Instance Data)
1.存儲內容:是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容
2.存儲范圍:無論是父類還是子類都要記錄
3.存儲順序:存儲順序會受到虛擬機分配策略參數(fieldsAllocationStyle)和字段定義順序的影響
4.默認分配策略順序:按字節由大到小(即由寬到窄),
longs/doubles->ints->shorts/chars->bytes/booleans->oops(Ordinary Object Pointers)普通對象指針
5.在滿足分配策略這個前提條件下,父類中定義的變量會出現在子類之前
6.如果CompactFields參數值為true,那么子類中較窄的變量也可能會插入到父類變量的空隙之中
3.3.對齊補充(Padding)
1.作用:對齊填充並不是必然存在的,也沒有特別的含義,它僅僅起着占位符的作用。
2.原因:由於HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說,就是對象的大小必須是8字節的整數倍。
3.解決:而對象頭部分正好是8字節的倍數(1倍或者2倍),因此,當對象實例數據部分沒有對齊時,就需要通過對齊填充來補全。
4.對象的訪問定位

對象的訪問定位
1.Java程序需要通過棧上的reference數據來操作堆上的具體對象。
2.由於reference類型在Java虛擬機規范中只規定了一個指向對象的引用,
3.並沒有定義這個引用應該通過何種方式去定位、訪問堆中的對象的具體位置,
4.所以對象訪問方式也是取決於虛擬機實現而定的。
5.目前主流的訪問方式有使用句柄和直接指針兩種。
6.總結:通過reference數據來定位具體對象,但reference只規定了對象的引用,並沒有提供具體實現

4.1.句柄方式
1.Java堆中將會划分出一塊內存來作為句柄池,
2.reference中存儲的就是對象的句柄地址,
3.而句柄中包含了對象實例數據與類型數據各自的具體地址信息。
優點:
使用句柄來訪問的最大好處就是reference中存儲的是穩定的句柄地址,
在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數據指針,而reference本身不需要修改。
4.2.直接指針方式
1.Java堆對象的布局中就必須考慮如何放置訪問類型數據的相關信息,
2.而reference中存儲的直接就是對象地址。
優點:
使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的時間開銷,
由於對象的訪問在Java中非常頻繁,因此這類開銷積少成多后也是一項非常可觀的執行成本。
4.3.案例
虛擬機Sun HotSpot 使用的是直接指針的方式!
