問:請依據自己的理解重點談談你對 Java 類加載、鏈接、初始化的理解?
答:一個 Java 類從字節碼到能在 JVM 中被使用需要經過加載、鏈接和初始化三個步驟,而從細節上來說又可以分為五個步驟,分別是加載、驗證、準備、解析、初始化。
對于我們寫代碼來說,直接可見的是 Java 類加載(使用 ClassLoader)步驟,而鏈接和初始化是在使用 Java 類之前的流程。
加載是由 ClassLoader 來完成的,ClassLoader 的主要作用是將 Java 字節碼轉換成 JVM 中的
java.lang.Class
類對象,其次通過雙親委派模式保證了類加載的唯一性和安全性;但是由于雙親委派的存在,所以啟動一個類加載過程的類加載器和最終定義這個類的類加載器可能并不是一個(前者稱為初始類加載器,后者稱為定義類加載器),故一個 Java 類的定義類加載器是該類所導入的其它 Java 類的初始類加載器(譬如 A、B 兩個類均未加載,A 中定義了 B 類型的成員,則 A 的定義類加載器會負責啟動 B 的加載過程),這個一定要理解。此外 JVM 判斷兩個java.lang.Class
是否相同不僅要依據類的全路徑描述符,還要保證是同一個 ClassLoader 加載。-
鏈接是將 Java 類的二進制代碼合并到 JVM 運行狀態中的過程,鏈接依賴于成功的加載流程,鏈接包括驗證、準備和解析等幾個步驟。
驗證過程是確保 Java 類二進制結構字節碼的正確性,如果驗證過程出現錯誤則會拋出
java.lang.VerifyError
錯誤。準備過程將創建 Java 類的靜態域,同時將這些域的值設為默認值,準備過程不會執行代碼。
解析過程確保類的繼承、組合等關聯類、接口能被正確找到,解析的過程可能會導致其它的 Java 類被加載(譬如在一個 Java 類中會包含對其它類或接口的引用或繼承,解析就是去確認這些被引用的類能被正確的找到)。不同 JVM 解析策略可能不同,有些是在鏈接的時候就遞歸對所有依賴進行解析,有些只會在真正用到時時才進行解析(譬如只在方法中使用到了其他類)。
初始化是指一個類第一次被使用時 JVM 會進行該類的初始化操作,初始化過程主要是執行類里面的靜態代碼塊和初始化靜態域。在一個類被初始化之前,它的直接父類也需要被初始化。但是,一個接口的初始化不會引起其父接口的初始化。在初始化的時候,會按照源代碼中從上到下的順序依次執行靜態代碼塊或初始化靜態域。
Java 類和接口的初始化過程只會在特定時機發生,具體如下:
創建一個未被使用過的 Java 類實例。
調用一個未被使用過的 Java 類靜態方法。
給一個未被使用過的 Java 類或接口中聲明的靜態域賦值。
訪問一個未被使用過的 Java 類或接口中聲明的靜態域且該域不是常值變量。
此外,一定要搞清楚類加載初始化流程與類實例化流程的區別,這是兩個東西,只有在某些情況下(譬如 new 一個初次使用的對象實例等)這兩者才會有直接關聯,而關聯關系也是先有類加載初始化流程,之后才有類實例化流程。
本文參考自 再談 Java 類加載、鏈接、初始化流程細節問題