在Java程式中,建立一個對象通常需要一個new關鍵字就夠了,但是在虛擬機器中,這個過程卻有點複雜,這裡麵包括了類載入、記憶體配置、初始化零值等等一系列的步驟。
下面來看看JVM如何建立一個對象(這裡面的對象僅僅限於不同的Java對象,不包括數組和Class對象) 1 對象的建立 1.1 類初始化
當JVM遇到一條new的指令(與new關鍵字不是一個概念)時,首先去檢查這個指令是否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經載入、解析和初始化過。如果沒有,那麼必須先執行類的初始化工作。
1.2 劃分空間
接下來就是要堆中劃分出一塊空間,這塊空間的大小由類去確定,在類載入以後,一個對象的大小已經是確定的了。對於這個劃分,可能存在兩種情況
(1)堆的空間是規整的,已用過的部分在一邊,未用過的部分在另外一邊,有一個指標指向未使用部分的頭,每次移動這個指標就可以了,這種方法稱為“指標碰撞”。
(2) 堆的空間是零散的,使用和未使用的部分交叉排列,這時候就需要一個維護一個列表,記錄哪些記憶體是可用的,在分配的時候找到一塊足夠大的記憶體進行分配,這種方法稱為“空閑列表”。很顯然這種方式會產生很多記憶體片段。
實際中採用哪種分配方式是由虛擬機器採用的垃圾收集演算法決定的,主要取決於垃圾收集器是否帶有壓縮整理功能(campact)。因此,在使用Serial,ParNew等待Compact過程的收集器時,系統採用指標碰撞,而使用CMS這種基於Mark-Sweep演算法的收集器時,採用空閑列表。
如何保證對象建立的執行緒安全性。
對象建立是虛擬機器中頻繁發生的行為,移動指標時如何保證安全執行緒呢。這個問題有兩個解決方案
1 採用CAS+失敗重試保證更新操作證的原子性
2 把記憶體配置動作按照線程劃分在不同空間中進行。在堆中為每個記憶體配置一小塊空間,稱為本地線程分配緩衝(TLAB)。線程分配記憶體時,在自己的TLAB上分配,當TLAB使用完時,使用同步鎖。 1.3 賦零值
記憶體配置完成後,虛擬機器把分配到的記憶體空間初始化為零值,如果使用TLAB,這一過程在TLAB中進行。這一步保證了Java的執行個體欄位在Java代碼中不同賦值就可以使用 1.4 設定對象頭
虛擬機器啊要對對象進行必要的設定,例如對象時哪個類的執行個體、如何才能找到類的中繼資料資訊、對象的雜湊碼、對象的GC分代年齡等資訊。這些資訊儲存在對象頭中。 1.5 <init>指令
在上面的工作完成後,對於虛擬機器來說,一個對象已經建立完成,帶從Java對象的角度,還沒有進行初始化,<init>方法還沒有執行,所有的欄位都還是零值。這個<init>方法可以理解為對象的構造方法 2 對象的記憶體布局
在HotSpot虛擬機器中,對象在記憶體中儲存的布局分為3個部分:對象頭(Header)、執行個體資料(Instance Data) 和對其填充 2.1 對象頭
對象頭包括兩個部分,第一個部分用於儲存對象自身的運行時資料,這個部分的長度在32位和64位的虛擬機器中分別為32bit和64bit,官方稱為“Mark Word”。對象頭被設計稱為與對象結構無關的一個資料結構,32bit的儲存內容如下:
HotSpot虛擬機器對象頭
| 儲存內容 |
標誌位 |
狀態 |
| 對象雜湊碼、對象分代資訊 |
01 |
未鎖定 |
| 指向鎖記錄的指標 |
00 |
輕量級鎖定 |
| 指向重量鎖的指標 |
10 |
重量級鎖定 |
| 空,不需要記錄i資訊 |
11 |
GC標記 |
| 偏向線程ID、偏向時間戳記、對象分代資訊 |
01 |
可偏向 |
對象頭的另一個部分是類型指標,即對象指向它的類別中繼資料的指標,虛擬機器通過這個指標確定這個對象指向哪個類的執行個體。並不是所有虛擬機器實現都必須在對象資料上保留這個類型指標,也就說,尋找對象的中繼資料資訊並不一定要經過對象本身。另外,如果對象是一個數組,那對象頭必須還有一塊用於記錄數組長度的資料。
2.2 執行個體資料域
這個部分是對象真正儲存的有效資訊,也就是程式碼中所定義的各種類型的欄位內容。無論是從父類繼承下來的,還是子類中定義的,都需要記錄起來。這部分的儲存順序受到虛擬機器分配策略參數和欄位在Java源碼中定義的順序的影響。HotSpot虛擬機器預設的分配策略為long/doubles、ints、shorts/chars,bytes/booleans、oops,從分配策略來看,相同寬度的欄位總是被分配在一起。在滿足這個前提下,父類的變數會出現在子類之前。如果指定了compactFileds參數為true,那麼子類之中較窄的變數可能會插入到父類變數的空隙之中。 2.3對齊填充
這個部分並不一定有,而且沒有什麼實際意義,僅僅是為了填充資料。HotSpot VM的自動記憶體管理系統要求對象起始地址必須是8位元組的整數倍,也就是每個對象佔得大小必須是8位元組的整數倍,如果執行個體域不滿足,就要補充對其。