一、物件建立過程
1、檢查類是否已被載入
JVM遇到new指令時,首先會去檢查這個指令引數能否在常量池中定位到這個類的符號引用,檢查這個符號引用代表的類是否已被載入、解析、初始化,若沒有,則進行類載入
2、為新物件分配記憶體
類載入檢查後,JVM為新物件在堆記憶體中分配空間,記憶體大小在類載入完成後便可確定。記憶體分配方式有以下幾種:
1)指標碰撞(Bump the Pointer):若堆記憶體規整的,已用的和空閒的各佔一邊,分配記憶體就是把指標作為分界點,指標往空閒的一邊移動物件大小的空間。
2)空閒列表(Free List):若堆記憶體不規整,JVM必須維護一個記錄可用記憶體塊的列表,分配記憶體時,把列表中一塊空間分配給物件,並更新表記錄。
以上兩種在併發情況下,存線上程安全問題,在給物件A分配記憶體時,指標還沒來得及修改,物件B又同時使用原來的指標來分配記憶體。解決方案有兩種:
1)給分配記憶體的動作同步處理:JVM使用CAS+失敗重試,保證更新操作的原子性。
2)本地執行緒分配緩衝(TLAB Thread Local Allocation Buffer):給每個執行緒在堆記憶體中預先分配已小塊記憶體,在需要分配記憶體的執行緒的TLAB上分配,TLAB用完並分配新的TLAB時,才同步鎖定。JVM通過設定 -XX:+UseTLAB來開啟。
3、將分配到的記憶體都初始化為零值(不含物件頭)
保證了物件的例項欄位在java程式碼中不賦初始值就可以直接使用。如果使用TLAB,這一步可提前到TLAB分配時進行。
4、對物件進行其他必要的設定
如設定物件頭的內容
5、執行java程式碼中<init>方法進行初始化
以上4步完成後,對於JVM來說,新的物件已經產生了,但是對於java程式來說,物件才剛剛開始建立。
二、物件的記憶體結構
1、物件頭
1.1 標識欄位 Mark Work
用於儲存物件自身的執行時資料,如HashCode,GC分代年齡,鎖狀態標誌等
1.2 型別指標 Klass Pointer
物件指向它的型別後設資料的指標,JVM通過這個指標確定該物件屬於哪個類的例項
如果物件是一個陣列,物件頭中還要有一塊用於記錄陣列長度的資料,因為陣列長度是不確定的,無法通過後設資料中的資訊推斷陣列大小。
2、例項資料
物件實際儲存的有效資訊,即程式碼中定義的欄位和父類繼承下來的,儲存順序受到JVM分配策略引數(-XX:FieldAllocationStyle)和程式碼中欄位定義順序影響
3、對齊填充
不是必然存在,僅僅是起佔位符作用;由於HotSpot虛擬機器的自動記憶體管理系統要求任何物件大小都必須是8位元組的整數倍,物件頭被設計成正好是8位元組的整數倍,因此例項資料部分沒有對齊8位元組的整數倍的話,就通過對齊填充來補全。
三、物件的訪問方式
java程式是通過java棧中的reference資料來操作堆中的具體物件
1、控制程式碼訪問
java堆中劃分一塊記憶體作為控制程式碼池,棧上的reference存的是物件的控制程式碼地址,控制程式碼池中包含物件例項資料和型別資料的地址資訊。
優點:垃圾收集移動物件時,只改變控制程式碼中例項資料指標,而reference本身不需要修改。
2、直接訪問
直接指標訪問,reference存的直接是物件的地址。不需要多一次間接訪問的開銷。
優點:速度快,節省一次指標定位的時間開銷。
HotSpot虛擬機器主要使用直接訪問進行物件訪問。
參考文獻:
1.《深入理解Java虛擬機器:JVM高階特性與最佳實踐(第3版)》