Java 對(duì)象創(chuàng)建過程
判斷是否加載、分配內(nèi)存(指針碰撞或者空閑鏈表)、初始化為零值、設(shè)置對(duì)象頭(實(shí)例是哪個(gè)類的實(shí)例、類的元信息位置、GC 分代年齡等)、init 方法。
對(duì)象創(chuàng)建的流程步驟包括:
- 虛擬機(jī)遇到一條 new 指令,首先檢查這個(gè)對(duì)應(yīng)的類能否在常量池中定位到一個(gè)類的符號(hào)引用;
- 判斷這個(gè)類是否已被加載、解析和初始化;
-
- 為這個(gè)新生對(duì)象在 Java 堆中分配內(nèi)存空間,其中 Java 堆分配內(nèi)存空間的方式主要有以下兩種:
- 指針碰撞
- 分配內(nèi)存空間包括開辟一塊內(nèi)存和移動(dòng)指針兩個(gè)步驟
- 非原子步驟可能出現(xiàn)并發(fā)問題,Java 虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性
- 空閑列表
- 分配內(nèi)存空間包括開辟一塊內(nèi)存和修改空閑列表兩個(gè)步驟
- 非原子步驟可能出現(xiàn)并發(fā)問題,Java 虛擬機(jī)采用 CAS 配上失敗重試的方式保證更新操作的原子性
- 將分配到的內(nèi)存空間都初始化為零;
-
- 設(shè)置對(duì)象頭相關(guān)數(shù)據(jù):
- GC 分代年齡
- 對(duì)象的哈希碼 hashCode
- 元數(shù)據(jù)信息
- 執(zhí)行對(duì)象方法
- 代碼分析對(duì)象執(zhí)行的過程
Java 虛擬機(jī)創(chuàng)建一個(gè)對(duì)象包含以下步驟:
- 給對(duì)象分配內(nèi)存;
- 將對(duì)象的實(shí)例變量自動(dòng)初始化為其變量類型的默認(rèn)值;
- 初始化對(duì)象,給實(shí)例變量賦予正確的初始值。
針對(duì)第三個(gè)步驟,JVM 可采用三種方式來初始化對(duì)象,采用何種方式取決于創(chuàng)建對(duì)象的方式:
- 如果對(duì)象是通過 clone() 方法創(chuàng)建的,那么 JVM 把原來被克隆的對(duì)象的實(shí)例變量的值拷貝到新對(duì)象中;
- 如果對(duì)象是通過 ObjectInputStream 類的 readObject() 方法創(chuàng)建的,那么 JVM 通過從輸入流中讀入的序列化數(shù)據(jù)來初始化那些非暫時(shí)性(non-transient)的實(shí)例變量;
- 如果實(shí)例變量在聲明時(shí)被顯式初始化,那么就把初始化值賦給實(shí)例變量,接著再執(zhí)行構(gòu)造方法。這是最常見的初始化對(duì)象的方式。
總結(jié)對(duì)象創(chuàng)建過程:
- 首次創(chuàng)建對(duì)象時(shí),類中的靜態(tài)方法/靜態(tài)字段首次被訪問時(shí),Java 解釋器必須先查找類路徑,以定位 .class 文件;
- 然后載入 .class(這將創(chuàng)建一個(gè) Class 對(duì)象),有關(guān)靜態(tài)初始化的所有動(dòng)作都會(huì)執(zhí)行。因此,靜態(tài)初始化只在 Class 對(duì)象首次加載的時(shí)候進(jìn)行一次;
- 當(dāng)用 new 方法創(chuàng)建對(duì)象時(shí),首先再堆上為對(duì)象分配足夠的存儲(chǔ)空間;
- 這塊存儲(chǔ)空間會(huì)被清零,這就自動(dòng)地將對(duì)象中的所有基本類型數(shù)據(jù)都設(shè)置成了缺省值(對(duì)數(shù)字來說就是 0,對(duì) boolean 和 str 也相同),而引用則被設(shè)置成了 null;
- 執(zhí)行所有出現(xiàn)于字段定義處的初始化動(dòng)作(非靜態(tài)對(duì)象的初始化);
- 執(zhí)行構(gòu)造器。
init 方法
Java 在編譯之后會(huì)在字節(jié)碼文件中生成 init 方法,稱之為實(shí)例構(gòu)造器,該實(shí)例構(gòu)造器會(huì)將語句塊,變量初始化,調(diào)用父類的構(gòu)造器等操作收斂到 init 方法中,收斂順序?yàn)椋?/p>
- 父類變量初始化
- 父類語句塊
- 父類構(gòu)造函數(shù)
- 子類變量初始化
- 子類語句塊
- 子類構(gòu)造函數(shù)
- 收斂到 init 方法的意思是:將這些操作放入到 init 中去執(zhí)行。
clinit 方法
Java 在編譯之后會(huì)在字節(jié)碼文件中生成 clinit 方法,稱之為類構(gòu)造器。類構(gòu)造器同實(shí)例構(gòu)造器一樣,也會(huì)將靜態(tài)語句塊,靜態(tài)變量初始化,收斂到 clinit 方法中,收斂順序?yàn)椋?/p>
- 父類靜態(tài)變量初始化
- 父類靜態(tài)語句塊
- 子類靜態(tài)變量初始化
- 子類靜態(tài)語句塊
若父類為接口,則不會(huì)調(diào)用父類的 clinit 方法。一個(gè)類可以沒有 clinit 方法。
clinit 方法是在類加載過程中執(zhí)行的,而 init 是在對(duì)象實(shí)例化執(zhí)行的,所以 clinit 一定比 init 先執(zhí)行。整個(gè)順序就是:
- 父類靜態(tài)變量初始化
- 父類靜態(tài)語句塊
- 子類靜態(tài)變量初始化
- 子類靜態(tài)語句塊
- 父類變量初始化
- 父類語句塊
- 父類構(gòu)造函數(shù)
- 子類變量初始化
- 子類語句塊
- 子類構(gòu)造函數(shù)