天天看點

Java類的加載過程

概述

在Java語言中,類型的加載、連接配接和初始化過程都是在程式運作期間完成的,這種政策雖然會令類加載時稍微增加一些性能開銷,但是會為Java應用程式提供高度的靈活性,Java裡天生可以動态擴充的語言特性就是依賴運作期動态加載和動态連結這個特點實作的。例如,如果編寫一個面向接口的應用程式,可以等到運作時再指定其實際的實作類:使用者可以通過Java預定義和自定義類加載器,讓一個本地的應用程式可以在運作時從網絡或其他地方加載一個二進制流作為程式代碼的一部分,這種組裝應用程式的方式已經廣發應用于Java程式之中。

類加載的時機

類從被加載到虛拟機記憶體中開始,到解除安裝出記憶體為止,他的整個生命周期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和解除安裝(Unloading)7個階段。其中驗證、準備、解析3個部分統稱為連接配接(Linking)。這7個階段的發生順序如圖所示:

Java類的加載過程

加載、驗證、準備、初始化和解除安裝這五個階段的順序是确定的,類的加載過程必須按照這種順序按部就班的開始,而解析階段則不一定:他在某些情況下可以在初始化階段之後再開始,這是為了支援Java語言的運作時綁定(也稱為動态綁定或晚期綁定)。注意,這裡寫的是按部就班的開始,而不是按部就班的進行或完成,強調這點是因為這些階段通常都是互相交叉的混合式進行的,通常會在一個階段執行的過程中調用,激活另外一個階段。

那什麼時候開始類加載過程的第一個階段?Java虛拟機并沒有進行強制限制,這點可以交給虛拟機的具體實作來自由把握。但是對于初始化階段,虛拟機規範則是嚴格規定了有且隻有5種情況必須立即對類進行初始化(而加載、驗證、準備自然需要在此之前開始):

  • 遇到new、getstatic、putstatic或invokestatic這4調位元組碼指令時,如果累沒有進行過初始化,則需要先出法其初始化。生成這4條指令的最常見場景是:使用new關鍵字執行個體化對象的時候、讀取或設定一個類的靜态字段的時候,以及調用一個類的靜态方法的時候。
  • 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化則需要先觸發其初始化。
  • 當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
  • 當虛拟機啟動時,使用者需要指定一個需要執行的主類,虛拟機會先初始化這個主類。
  • 當使用動态語言的時候,如果一個java.lang.invoke.MethodHandle執行個體最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的的類沒有進行過初始化,則需要先觸發其初始化。

除此之外,所有引用類的方式都不會觸發初始化,成為被動引用。

類加載的過程

加載

在加載階段,虛拟機需要完成以下3件事情:

  • 通過一個類的全限定名來擷取定義此類的二進制位元組流。
  • 将這個位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構。
  • 在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種資料的通路入口。

驗證

驗證是連接配接階段的第一步,這一階段的目的是為了確定Class檔案的位元組流中包含的資訊渡河目前虛拟機的藥企,并且不會危害虛拟機自身的安全。

驗證階段大緻上會完成下面4個階段的檢驗動作:

  • 檔案格式驗證:是否符合Class檔案格式的規範,并且能被目前版本的虛拟機處理。
  • 中繼資料驗證:對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合Java語言規範的要求。
  • 位元組碼驗證:整個驗證過程中最複雜的階段,主要目的是通過資料流和控制流分析,确定程式語義是合法的、符合邏輯的。
  • 符号引用驗證:最後一個階段的校驗發生在虛拟機将符号引用轉化為直接引用的時候,這個轉化動作将在連接配接的第三個階段–解析階段中發生。符号引用驗證可以看做是對類自身以外的資訊進行比對性校驗。

準備

準備階段是正式為類的變量配置設定記憶體并設定類變量初始值的階段,這些變量所使用的記憶體都将在方法區中進行配置設定。這個階段中有兩個容易産生混淆的概念需要強調一下,首先,這時候進行記憶體配置設定的僅包括類變量(被static修飾的變量),而不包括執行個體變量,執行個體變量将會在對象執行個體化時随着對象一起配置設定在Java堆中。

解析

解析階段是hi虛拟機将常量池内的符号引用替換為直接引用的過程,那直接引用于符号引用又有什麼關聯呢?

  • 符号引用:以一組符号來描述所引用的目标,符号可以使任何形式的字面量,隻要使用時能無歧義的定位到目标即可。符号引用與虛拟機實作的記憶體布局無關,引用的目标并不一定已經加載到記憶體中。
  • 直接引用:直接引用可以使直接指向目标的指針、相對偏移量或是一個能間接定位到目标的句柄。直接引用是和虛拟機實作的記憶體布局相關的,同一個符号引用在不同虛拟機執行個體上翻譯出來的直接引用一般不會相同。如果有了直接引用,那引用的目标必定已經在記憶體中存在。

解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符号引用進行。

初始化

類初始化階段是類架子啊過程的最後一步 ,前面的類加載過程中,出了在加載階段使用者應用程式可以通過自定義類加載器參與之外,其餘動作完全由虛拟機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程式代碼。

在準備階段,變量已經指派過一次系統要求的初始值(零值),而在初始化階段,則根據程式員通過程式制定的主觀計劃去初始化類變量和其他資源,或者可以從另外一個角度來表達:初始化階段是執行類構造器()方法的過程。