7. 物件的例項化記憶體佈局與訪問定位
一、物件的例項化
1.1 建立物件的步驟
-
判斷物件是否載入、連結、初始化
虛擬機器遇到一條new指令,首先回檢查這個指令的引數能否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入、解析、初始化。如果沒有,就執行相應類的載入過程。
-
為物件分配記憶體
如果記憶體是規整的:
“指標碰撞”:把記憶體分為已使用和未使用的兩部分,中間用指標分割,分配記憶體的時候就把指標向空閒方向移動一段與物件記憶體大小相等的距離。
如果記憶體是不規整的:
“空閒列表”:用一個列表記錄哪些記憶體塊是可用的,在分配的時候從列表中找出一塊足夠大的空間分配給物件例項,並更新列表的記錄。
選擇哪種分配方式由 Java 堆是否規整決定,而 Java 堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。
-
處理併發安全問題
在併發情況下可能會導致分配的記憶體被覆蓋,可以使用兩種方式解決
-
CAS + 失敗重試:保證原子性
-
TLAB 把記憶體分配的動作按照執行緒劃分在不同的空間中進行,即每個執行緒在 Java 堆中預先分配一小塊記憶體,稱為本地執行緒緩衝區,哪個執行緒要分配記憶體,就在哪個執行緒的本地緩衝區中分配,只有當TLAB用完了,分配新的緩衝區時才需要同步鎖定。
-
-
初始化分配到的空間
記憶體分配完成之後,虛擬機器必須將分配到的記憶體空間(不包括物件頭)都初始化為零值,這步操作保證了物件的例項欄位在 Java 程式碼中可以不賦初始值就可以直接使用。
-
設定物件的物件頭
將物件的所屬類、物件的hashcode、物件的GC資訊、鎖資訊等資料儲存在物件的物件頭中。
-
執行init方法進行初始化
上面幾步執行完之後,從虛擬機器的角度來看,一個新的物件已經產生了。但是從 Java 程式的角度來看,物件建立才剛剛開始,物件需要的其他資源和狀態資源也還沒有按照預定的意圖構造好。執行 () 方法,按照程式設計師的意願對物件進行初始化,這樣一個真正可用的物件才算完全被構造出來。
二、物件的記憶體佈局
-
物件頭
元資料:
雜湊值、GC分代年齡、指向鎖記錄的指標、指向重量級鎖的指標、偏向執行緒ID、偏向時間戳
型別指標:
型別指標就是物件指向他的型別元資料的指標,Java虛擬機器通常通過這個指標來確定該物件是哪個類的例項
-
例項資料
它是物件真正儲存的有效資訊,包括程式程式碼中定義的各種型別的欄位(包括從父類繼承下來的和本身擁有的欄位)
儲存順序:
- 相同寬度的欄位分配到一起存放:long/double、float、int、short、chars、byte/boolean、oops
- 父類中定義的變量出現在子類之前
- 如果+XX:CompactFields引數值為true,那子類之中較窄的變數也允許插入父類變數的空隙之中,以節省空間
-
對齊填充
起佔位符的作用
三、物件的訪問定位
- 控制代碼訪問
Java 堆中劃分出一塊記憶體來作為控制代碼池,reference 中儲存的是物件的控制代碼地址,而控制代碼中包含了物件的例項資料與型別資料各自的地址。
優點:當移動物件(GC中狠普遍的行為)的時候,只需改變例項資料的指標,而不需要更改reference
缺點:訪問速度低
- 直接訪問(HotSpot使用的方式)
reference中儲存的直接就是物件地址,如果之訪問物件本身的話,就不需要多一次間接訪問的開銷。