1. 實例化步驟
- 對象創建的方式
1. new
2. Class的newInstance()
3. Contructor的newInstance(xxx)
4. 使用clone
5. 使用反序列化
6. 第三方庫Objenesis
- 對象的創建步驟
1. 判斷對象對應的類是否加載,鏈接,初始化,沒有加載,使用雙親委派機制加載類
2. 爲對象分配內存
不同的垃圾收集器會導致,內存規整或不規整,不同的內存結構,會有不同的分配方式
2.1 內存規整-指針碰撞
2.2 內存不規整-維護空閒列表
3. 處理併發安全問題,內存分配必須是線程安全的,同一塊堆內存,同一時間只能由一個線程使用
3.1 採用CAS保證原子性
3.2 分配TLAB
4. 初始化分配到的空間-零值初始化,爲所有屬性設置默認值, 保證對象實例字段不賦值可以直接使用
5. 設置對象的對象頭
6. 執行init方法進行初始化(構造方法)
2. 內存佈局
在java堆內存上的對象由三部分組成
1. 對象頭
2. 實例數據
3. 對齊填充
- 對象頭
包含兩部分數據,如果是數組,還要記錄數組的長度
1. 運行時元數據(Mark Word)
1.1 哈希值(HashCode)
1.2 GC分代年齡
1.3 鎖狀態標誌
1.4 線程持有的鎖
1.5 偏向線程ID
1.6 偏向時間戳
2. 類型指針
指向類元數據InstanceKlass,確定該對象所屬的類型
- 實例數據
對象真正存儲有效信息的地方,包括程序代碼中定義的各種類型的字段,包括從父類繼承下來的和本身擁有的字段
存儲規則
1. 相同寬度的字段總是被分配到一起
2. 父類中定義的變量會出現在子類之前
3. 如果CompactFields參數爲true,子類的窄變量可能插入到父類變量的空隙
-
對齊填充
沒有特殊含義,僅僅起到佔位符的作用 -
實例
public class Customer{
int id = 1001;
String name;
Account acct;
{
name = "匿名客戶";
}
public Customer(){
acct = new Account();
}
}
class Account{
}
上述代碼的內存佈局如下圖所示
1. main方法的棧幀被壓入棧,棧幀中的局部變量有args和cust兩個變量
2. cust指向堆空間的Custtomer實例
3. Customer實例包含三部分信息:對象頭,實例數據和對齊填充
4. 對象頭重的類型信息指向方法區的類元信息
5. 實例數據的字符串指向字符串常量池
3. 對象訪問定位
jvm如何通過棧幀中的對象引用訪問到其內部的對象實例
訪問方式
- 句柄訪問
1. java堆區中額外維護一份句柄信息,指向堆中對象和對象類型
2. 虛擬機棧中的局部變量表保存句柄信息
- 直接指針(HotSpot採用)
棧中局部變量直接保存對象指針信息
兩種訪問方式比較
1. 直接指針方式,不維護句柄信息,節省空間,訪問更快
2. 使用句柄池方式,棧中局部變量保存的地址始終不用變動
堆中對象移動時,直接地址方式保存地址需要改變地址,句柄方式則不用