從虛擬機器的角度看java物件的建立
前言
建立物件有多種方式,最直觀的方式就是通過new關鍵字建立物件。通過new建立的物件儲存在java的堆中。
物件的建立過程
java建立過程要經過下圖的6個步驟,我們所能看到的是new 類名和呼叫物件的初始化方法,中間的四個是虛擬機器內部所執行的,對於開發者來講它遮蔽了。
將分配的記憶體初始化為零值就是建立完物件後,基本型別都有預設值,抽象資料型別預設值為null。
Init方法就是程式碼塊,包括構造方法。
給物件分配記憶體
給物件分配記憶體有兩種指標碰撞和空閒列表兩種。
指標碰撞
右邊為使用記憶體,左邊為空閒記憶體,兩個指標之間為建立所需要的記憶體,當兩個指標碰到後,即物件建立完成。具體過程如下圖。
空閒列表
Java堆不是規整的,以使用的記憶體和空閒的記憶體不是上圖中那麼有規矩的,而是相互交錯的。這個時候就不能使用指標碰撞了。這個時候虛擬機器必須維護一個列表,記錄哪些記憶體塊是可用的,那麼分配的時候可以從表中找出來一塊區域給物件的例項並更新在表中。
記憶體的分配方式是由Java的堆是否規整決定的,而Java的堆是否規整決定是由垃圾回收策略決定的。
執行緒安全問題
在多執行緒情況下,如果發生執行緒安全問題有兩種解決辦法:執行緒同步和本地執行緒分配緩衝(TLAB)
執行緒同步
在併發情況下給每個建立執行緒加鎖。
本地執行緒分配緩衝(TLAB)
在堆記憶體中,給每個執行緒都分配一個自己的區域,每個執行緒操作不同的區域,就不會導致執行緒安全問題。
物件的結構
物件結構由Header,InstanceData和Padding三部分組成。
Header(物件頭)
自身執行時資料 (Mark Word)
雜湊值、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒ID、偏向時間戳等.。
執行時資料佔的記憶體根據32位64位虛擬機器的不同,分別佔32位和64位。
型別指標
物件指向類源資料的指標,這個不是必須需要的。陣列這種型別的他還會儲存資料的長度。
我們是無法操作物件頭的。
InstanceData(例項資料)
相同寬度的欄位分配到一起,比如說long和double會分配到一塊,short和char分配到一塊。在父類定義的變數會出現在子類之前。
Padding(填充)
對齊填充,這部分的資料不是必然存在的,也沒有特別含義,相當於佔位符。主要是因為HotSpot的自動記憶體管理系統要求物件起止地址必須是8個位元組的整數倍,也就是說物件必須是8個位元組的整數倍。而物件頭部正好是8個位元組的整數倍。因為例項資料如果沒有對齊就需要這個填充。
物件的訪問定位
物件的訪問定位有兩種,分別是使用控制代碼和直接指標。HotSpot採用的就是直接指標的方式。
使用控制代碼
引用指向堆中的控制代碼池,其中控制代碼池儲存了例項物件的地址。
直接指標
使用直接指標就是引用型別直接指向真正的物件記憶體區域
存在控制代碼池為什麼還要使用直接指標?
使用控制代碼池:引用儲存的地址一定是固定的,不管堆中物件的記憶體被垃圾回收,還是移動位置等,引用變數儲存的地址是不會變的。變的只是控制代碼池的地址。
使用直接指標:訪問非常快,減少一次定址的過程,減少效能開銷,效能比較高。
使用控制代碼需要儲存到物件例項資料的指標和到物件型別資料的指標,如果是使用直接指標,則只需要儲存到物件型別資料的指標即可。