Java 對(duì)象創(chuàng)建過程。init 方法和 clinit方法。

Java 對(duì)象創(chuàng)建過程

判斷是否加載、分配內(nèi)存(指針碰撞或者空閑鏈表)、初始化為零值、設(shè)置對(duì)象頭(實(shí)例是哪個(gè)類的實(shí)例、類的元信息位置、GC 分代年齡等)、init 方法。

對(duì)象創(chuàng)建的流程步驟包括:

    1. 虛擬機(jī)遇到一條 new 指令,首先檢查這個(gè)對(duì)應(yīng)的類能否在常量池中定位到一個(gè)類的符號(hào)引用;
    1. 判斷這個(gè)類是否已被加載、解析和初始化;
    1. 為這個(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 配上失敗重試的方式保證更新操作的原子性
    1. 將分配到的內(nèi)存空間都初始化為零;
    1. 設(shè)置對(duì)象頭相關(guān)數(shù)據(jù):
    • GC 分代年齡
    • 對(duì)象的哈希碼 hashCode
    • 元數(shù)據(jù)信息
    1. 執(zhí)行對(duì)象方法
    1. 代碼分析對(duì)象執(zhí)行的過程

Java 虛擬機(jī)創(chuàng)建一個(gè)對(duì)象包含以下步驟:

    1. 給對(duì)象分配內(nèi)存;
    1. 將對(duì)象的實(shí)例變量自動(dòng)初始化為其變量類型的默認(rèn)值;
    1. 初始化對(duì)象,給實(shí)例變量賦予正確的初始值。

針對(duì)第三個(gè)步驟,JVM 可采用三種方式來初始化對(duì)象,采用何種方式取決于創(chuàng)建對(duì)象的方式:

    1. 如果對(duì)象是通過 clone() 方法創(chuàng)建的,那么 JVM 把原來被克隆的對(duì)象的實(shí)例變量的值拷貝到新對(duì)象中;
    1. 如果對(duì)象是通過 ObjectInputStream 類的 readObject() 方法創(chuàng)建的,那么 JVM 通過從輸入流中讀入的序列化數(shù)據(jù)來初始化那些非暫時(shí)性(non-transient)的實(shí)例變量;
    1. 如果實(shí)例變量在聲明時(shí)被顯式初始化,那么就把初始化值賦給實(shí)例變量,接著再執(zhí)行構(gòu)造方法。這是最常見的初始化對(duì)象的方式。

總結(jié)對(duì)象創(chuàng)建過程:

  1. 首次創(chuàng)建對(duì)象時(shí),類中的靜態(tài)方法/靜態(tài)字段首次被訪問時(shí),Java 解釋器必須先查找類路徑,以定位 .class 文件;
  2. 然后載入 .class(這將創(chuàng)建一個(gè) Class 對(duì)象),有關(guān)靜態(tài)初始化的所有動(dòng)作都會(huì)執(zhí)行。因此,靜態(tài)初始化只在 Class 對(duì)象首次加載的時(shí)候進(jìn)行一次;
  3. 當(dāng)用 new 方法創(chuàng)建對(duì)象時(shí),首先再堆上為對(duì)象分配足夠的存儲(chǔ)空間;
  4. 這塊存儲(chǔ)空間會(huì)被清零,這就自動(dòng)地將對(duì)象中的所有基本類型數(shù)據(jù)都設(shè)置成了缺省值(對(duì)數(shù)字來說就是 0,對(duì) boolean 和 str 也相同),而引用則被設(shè)置成了 null;
  5. 執(zhí)行所有出現(xiàn)于字段定義處的初始化動(dòng)作(非靜態(tài)對(duì)象的初始化);
  6. 執(zhí)行構(gòu)造器。

init 方法

Java 在編譯之后會(huì)在字節(jié)碼文件中生成 init 方法,稱之為實(shí)例構(gòu)造器,該實(shí)例構(gòu)造器會(huì)將語句塊,變量初始化,調(diào)用父類的構(gòu)造器等操作收斂到 init 方法中,收斂順序?yàn)椋?/p>

  1. 父類變量初始化
  2. 父類語句塊
  3. 父類構(gòu)造函數(shù)
  4. 子類變量初始化
  5. 子類語句塊
  6. 子類構(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>

  1. 父類靜態(tài)變量初始化
  2. 父類靜態(tài)語句塊
  3. 子類靜態(tài)變量初始化
  4. 子類靜態(tài)語句塊
  • 若父類為接口,則不會(huì)調(diào)用父類的 clinit 方法。一個(gè)類可以沒有 clinit 方法。

  • clinit 方法是在類加載過程中執(zhí)行的,而 init 是在對(duì)象實(shí)例化執(zhí)行的,所以 clinit 一定比 init 先執(zhí)行。整個(gè)順序就是:

  1. 父類靜態(tài)變量初始化
  2. 父類靜態(tài)語句塊
  3. 子類靜態(tài)變量初始化
  4. 子類靜態(tài)語句塊
  5. 父類變量初始化
  6. 父類語句塊
  7. 父類構(gòu)造函數(shù)
  8. 子類變量初始化
  9. 子類語句塊
  10. 子類構(gòu)造函數(shù)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容