1. 程式人生 > 其它 >Java 物件例項化過程 學習筆記 2022-3-25

Java 物件例項化過程 學習筆記 2022-3-25

學習地址:https://mp.weixin.qq.com/s/JD298UZ-1q8YuV5ft3RDWA

1、物件的例項化過程:類的載入與初始化 + 物件的初始化

  • 物件的例項化過程是分成兩部分:類的載入與初始化 以及 物件的初始化
  • 一個子類要初始化需要先初始化父類
  • 要建立類的物件例項需要先載入並初始化該類,main方法所在的類需要先載入和初始化
  • 類初始化就是執行< clinit >方法,物件例項化是執行< init >方法

2、類的載入過程

類的載入機制:如果沒有相應類的class,則載入class到方法區。對應著載入->驗證->準備->解析-->初始化階段

載入:載入class物件,不一定是從class檔案獲取,可以是jar包,或者動態生成的class

連線:驗證、準備、解析3個部分稱為 連線

  • 驗證:校驗class位元組流是否符合當前jvm規範
  • 準備:為 類變數 分配記憶體並設定變數的初始值( 預設值 )。如果是final修飾的物件則是賦值宣告值
  • 解析:將常量池的符號引用替換為直接引用

初始化:執行類構造器< client >( 注意不是物件構造器 ),為 類變數 賦值,執行靜態程式碼塊。jvm會保證子類的< client >執行之前,父類的< client >先執行完畢;< clinit >方法由 靜態變數賦值程式碼和靜態程式碼塊 組成;先執行類靜態變數顯示賦值程式碼,再到靜態程式碼塊程式碼

3、觸發類載入的條件

1、第一次建立類的新物件時, 會觸發類的載入初始化和物件的初始化函式< init >執行(例項初始化)
2、JVM啟動時會先載入初始化包含main方法的類
3、呼叫類的靜態方法(如執行invokestatic指令),對類或介面的靜態欄位執行讀寫操作(即執行getstatic、putstatic指令);不過final修飾的靜態欄位的除外(已經賦值,String和基本型別,不包含包裝型別),它被初始化為一個編譯時常量表達式

  • 注意 :操作靜態欄位時,只有直接定義這個欄位的類才會被初始化;如通過其子類來操作父類中定義的靜態欄位,只會觸發父類< clinit >的初始化而不是子類的初始化
    4、呼叫JavaAPI中的反射方法時(比呼叫java.lang.Class中的方法(Class.forName),或者java.lang.reflect包中其他類的方法)
    5、當初始化一個類時,其父類沒有初始化,則需先觸發父類的初始化(介面例外)

4、物件的例項化過程

  • 物件例項化過程其實就是執行類建構函式 對應在位元組碼檔案中的< init >()方法(稱之為例項構造器);< init >()方法由 非靜態變數、非靜態程式碼塊以及對應的構造器組成
    • < init >()方法可以過載多個,類有幾個構造器就有幾個()方法
    • < init >()方法中的程式碼執行順序為:父類變數初始化,父類程式碼塊,父類構造器,子類變數初始化,子類程式碼塊,子類構造器。
  • 靜態變數,靜態程式碼塊,普通變數,普通程式碼塊,構造器的執行順序

  • 具有父類的子類的例項化順序如下

5、類載入器和雙親委派規則,如何打破雙親委派規則

  • 類載入器
    通過一個類的全限定名來獲取描述此類的二進位制位元組流 ,實現這個動作的程式碼模組稱為類載入器
    任意一個類都需要其載入器和類本身來確定類在JVM的唯一性;每個類載入器都有自己的類名稱空間,同一個類class由不同的載入器載入,則被JVM判斷為不同的類

  • 雙親委派模型
    啟動類載入器由C++程式碼實現,是虛擬機器的一部分 ---> 負責載入lib下的類庫(JDK中的本地方法類)

其他的類載入器由java語言實現,獨立於JVM,並且繼承ClassLoader

  • extention ClassLoader ---> 負責載入libext目錄下的類庫(JDK內部實現的擴充套件類)
  • application ClassLoader ---> 負責載入使用者路徑下(ClassPath)的程式碼(程式中的類檔案)

問題:不同的類載入器載入同一個class檔案會導致出現兩個類

java給出解決方法是下層的載入器加委託上級的載入器去載入類,如果父類無法載入(在自己負責的目錄找不到對應的類),而交還下層類載入器去載入;
如下圖

打破雙親委派模型

  • 雙親委派模型並不是一個強制的約束模型,而是java設計者推薦給開發者的類載入實現方式
  • 雙親委派模型很好的解決各個類載入基礎類的同一問題(越基礎的類由越上層的載入器載入),但是基礎類總是作為使用者程式碼呼叫的API,但是如果它的具體實現是下層的程式碼,此時基礎類需要呼叫下層的程式碼,則需要打破雙親委派模型
  • 如JNDI服務,JNDI的程式碼有啟動類去載入(rt.jar),它需要呼叫由獨立廠商部署在應用程式classpath下的JNDI的SPI(Service Provider Interface)程式碼。為了解決SPI程式碼載入問題,java引入了執行緒上下文類載入器去載入SPI程式碼。也就是父類載入器請求子類去完成類的載入動作
  • 執行緒上下文類載入器,執行緒建立時會從父執行緒繼承,如果全域性範圍沒有設定過,則預設設定為application Class Loader