Java 程序創建過程
從 class 文件到內存中的類,按先后順序需要經過加載、鏈接以及初始化三大步驟。其中,鏈接過程中同樣需要驗證;而內存中的類沒有經過初始化,同樣不能使用。那么,是否所有的 Java 類都需要經過這幾步呢?
我們知道 Java 語言的類型可以分為兩大類:基本類型(primitive types)和引用類型(reference types)。在上一篇中,我已經詳細介紹過了 Java 的基本類型,它們是由 Java 虛擬機預先定義好的。
至于另一大類引用類型,Java 將其細分為四種:類、接口、數組類和泛型參數。由于泛型參數會在編譯過程中被擦除(我會在專欄的第二部分詳細介紹),因此 Java 虛擬機實際上只有前三種。在類、接口和數組類中,數組類是由 Java 虛擬機直接生成的,其他兩種則有對應的字節流。
說到字節流,最常見的形式要屬由 Java 編譯器生成的 class 文件。除此之外,我們也可以在程序內部直接生成,或者從網絡中獲取(例如網頁中內嵌的小程序 Java applet)字節流。這些不同形式的字節流,都會被加載到 Java 虛擬機中,成為類或接口。為了敘述方便,下面我就用“類”來統稱它們。
無論是直接生成的數組類,還是加載的類,Java 虛擬機都需要對其進行鏈接和初始化。
其實,Java 虛擬機將字節流轉化為 Java 類的過程,就是我們常說的Java類的創建過程。這個過程可分為加載、鏈接以及初始化三大步驟:
1.加載是指查找字節流,并且據此創建類的過程。加載需要借助類加載器,在 Java 虛擬機中,類加載器使用了雙親委派模型,即接收到加載請求時,會先將請求轉發給父類加載器。
2.鏈接,是指將創建成的類合并至 Java 虛擬機中,使之能夠執行的過程。鏈接還分驗證、準備和解析三個階段。其中,解析階段為非必須的。
3.初始化,則是為標記為常量值的字段賦值,以及執行 < clinit > 方法的過程。類的初始化僅會被執行一次,這個特性被用來實現單例的延遲初始化。
3.Java 程序加載過程
從虛擬機視角來看,執行 Java 代碼首先需要將它編譯而成的 class 文件加載到 Java 虛擬機中。加載后的 Java 類會被存放于方法區(Method Area)中。實際運行時,虛擬機會執行方法區內的代碼。
如果你熟悉 X86 的話,你會發現這和段式內存管理中的代碼段類似。而且,Java 虛擬機同樣也在內存中劃分出堆和棧來存儲運行時數據。
不同的是,Java 虛擬機會將棧細分為面向 Java 方法的 Java 方法棧,面向本地方法(用 C++ 寫的 native 方法)的本地方法棧,以及存放各個線程執行位置的 PC 寄存器。
在運行過程中,每當調用進入一個 Java 方法,Java 虛擬機會在當前線程的 Java 方法棧中生成一個棧幀,用以存放局部變量以及字節碼的操作數。這個棧幀的大小是提前計算好的,而且 Java 虛擬機不要求棧幀在內存空間里連續分布。
當退出當前執行的方法時,不管是正常返回還是異常返回,Java 虛擬機均會彈出當前線程的當前棧幀,并將之舍棄。
從硬件視角來看,Java 字節碼無法直接執行。因此,Java 虛擬機需要將字節碼翻譯成機器碼。
啟動類加載器是由 C++ 實現的,沒有對應的 Java 對象,因此在 Java 中只能用 null 來指代。
除了啟動類加載器之外,其他的類加載器都是 java.lang.ClassLoader 的子類,因此有對應的 Java 對象。這些類加載器需要先由另一個類加載器,比如說啟動類加載器,加載至 Java 虛擬機中,方能執行類加載。
在 Java 虛擬機中,這個潛規則有個特別的名字,叫雙親委派模型。每當一個類加載器接收到加載請求時,它會先將請求轉發給父類加載器。在父類加載器沒有找到所請求的類的情況下,該類加載器才會嘗試去加載。
在 Java 9 之前,啟動類加載器負責加載最為基礎、最為重要的類,比如存放在 JRE 的 lib 目錄下 jar 包中的類(以及由虛擬機參數 -Xbootclasspath 指定的類)。除了啟動類加載器之外,另外兩個重要的類加載器是擴展類加載器(extension class loader)和應用類加載器(application class loader),均由 Java 核心類庫提供。
擴展類加載器的父類加載器是啟動類加載器。它負責加載相對次要、但又通用的類,比如存放在 JRE 的 lib/ext 目錄下 jar 包中的類(以及由系統變量 java.ext.dirs 指定的類)。
應用類加載器的父類加載器則是擴展類加載器。它負責加載應用程序路徑下的類。(這里的應用程序路徑,便是指虛擬機參數 -cp/-classpath、系統變量 java.class.path 或環境變量 CLASSPATH 所指定的路徑。)默認情況下,應用程序中包含的類便是由應用類加載器加載的。
Java 9 引入了模塊系統,并且略微更改了上述的類加載器1。擴展類加載器被改名為平臺類加載器(platform class loader)。Java SE 中除了少數幾個關鍵模塊,比如說 java.base 是由啟動類加載器加載之外,其他的模塊均由平臺類加載器所加載。
除了由 Java 核心類庫提供的類加載器外,我們還可以加入自定義的類加載器,來實現特殊的加載方式。舉例來說,我們可以對 class 文件進行加密,加載時再利用自定義的類加載器對其解密。
除了加載功能之外,類加載器還提供了命名空間的作用。在 Java 虛擬機中,類的唯一性是由類加載器實例以及類的全名一同確定的。即便是同一串字節流,經由不同的類加載器加載,也會得到兩個不同的類。在大型應用中,我們往往借助這一特性,來運行同一個類的不同版本。
更多關于“java培訓”的問題,歡迎咨詢千鋒教育在線名師。千鋒教育多年辦學,課程大綱緊跟企業需求,更科學更嚴謹,每年培養泛IT人才近2萬人。不論你是零基礎還是想提升,都可以找到適合的班型,千鋒教育隨時歡迎你來試聽。