天天看點

Java虛拟機類加載機制淺談

   Java語言是一種編譯後再經過解釋器執行的過程, 解釋器主要就是如何處了解釋Class檔案的二進制位元組流。JVM主要包含三大核心部分:運作時資料區,類加載器和執行引擎。

       虛拟機将描述類的資料從Class檔案加載到記憶體,并對資料進行校驗、準備、解析和初始化,最終就會形成可以被虛拟機使用的Java類型,這就是一個虛拟機的類加載機制。Java中的類是動态加載的,隻有在運作期間使用到該類的時候,才會将該類加載到記憶體中,Java依賴于運作期動态加載和動态連結來實作類的動态使用。

一個類的整個生命周期如下:

<a target="_blank" href="http://blog.51cto.com/attachment/201306/082143925.jpg"></a>

    加載,驗證,準備,初始化和解除安裝在開始的順序上是固定的,但是可以交叉進行。

   在Java中,對于類有且僅有四種情況會對類進行“初始化”。

     1) 使用new關鍵字執行個體化對象的時候,讀取或設定一個類的靜态字段時候(除final修飾的static外),調用類的靜态方法時候,都隻會初始化該靜态字段或者靜态方法所定義的類。

     2) 使用reflect包對類進行放射調用的時候,如果類沒有進行初始化,則先要初始化該類

     3) 當初始化一個類的時候,如果其父類沒有初始化過,則先要觸發其父類初始化。

     4)  虛拟機啟動的時候,會初始化一個有main方法的主類。

注意:通過子類引用父類靜态字段,隻會初始化父類不會初始化子類;通過數組定義來引用類,也不會觸發該類的初始化;常量在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類,是以也不會觸發定義常量的類的初始化。

一 類加載過程

1 加載

       加載階段主要完成三件事,即通過一個類的全限定名來擷取定義此類的二進制位元組流,将這個位元組流所代表的靜态存儲結構轉化為方法區的運作時資料結構,在Java堆中生成一個代表此類的Class對象,作為通路方法區這些資料的入口。這個加載過程主要就是靠類加載器實作的,這個過程可以由使用者自定義類的加載過程。

2 驗證

這個階段目的在于確定Class檔案的位元組流中包含資訊符合目前虛拟機要求,不會危害虛拟機自身安全。主要包括四種驗證:

     檔案格式驗證:基于位元組流驗證,驗證位元組流是否符合Class檔案格式的規範,并且能被目前虛拟機處理。

     中繼資料驗證:基于方法區的存儲結構驗證,對位元組碼描述資訊進行語義驗證。

     位元組碼驗證:基于方法區的存儲結構驗證,進行資料流和控制流的驗證。

     符号引用驗證:基于方法區的存儲結構驗證,發生在解析中,是否可以将符号引用成功解析為直接引用。

3 準備

僅僅為類變量(即static修飾的字段變量)配置設定記憶體并且設定該類變量的初始值即零值,這裡不包含用final修飾的static,因為final在編譯的時候就會配置設定了,同時這裡也不會為執行個體變量配置設定初始化。類變量會配置設定在方法區中,而執行個體變量是會随着對象一起配置設定到Java堆中。

4 解析

解析主要就是将常量池中的符号引用替換為直接引用的過程。符号引用就是一組符号來描述目标,可以是任何字面量,而直接引用就是直接指向目标的指針、相對偏移量或一個間接定位到目标的句柄。有類或接口的解析,字段解析,類方法解析,接口方法解析。

 這裡要注意如果有一個同名字段同時出現在一個類的接口和父類中,那麼編譯器一般都會拒絕編譯。

5 初始化

    初始化階段依舊是初始化類變量和其他資源,這裡将執行使用者的static字段和靜态語句塊的指派操作。這個過程就是執行類構造器&lt;clinit&gt;方法的過程。

&lt;clinit&gt;方法是由編譯器收集類中所有類變量的指派動作和靜态語句塊的語句生成的,類構造器&lt;clinit&gt;方法與執行個體構造器&lt;init&gt;方法不同,這裡面不用顯示的調用父類的&lt;clinit&gt;方法,父類的&lt;clinit&gt;方法會自動先執行于子類的&lt;clinit&gt;方法。即父類定義的靜态語句塊和靜态字段都要優先子類的變量指派操作。

二 類加載器

1   類加載器的分類

       啟動類加載器(Bootstrap ClassLoader):  主要負責加載&lt;JAVA_HOME&gt;\lib目錄中的,或是-Xbootclasspath參數指定的路徑中的,并且可以被虛拟機識别(僅僅按照檔案名識别的)的類庫到虛拟機記憶體中。它加載的是System.getProperty("sun.boot.class.path")所指定的路徑或jar。

       擴充類加載器(Extension ClassLoader):主要負責加載&lt;JAVA_HOME&gt;\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫。它加載的是

System.getProperty("java.ext.dirs")所指定的路徑或jar。

       應用程式類加載器(Application ClassLoader):也叫系統類加載器,主要負責加載ClassPath路徑上的類庫,如果應用程式沒有自定義自己類加載器,則這個就是預設的類加載器。

它加載的是System.getProperty("java.class.path")所指定的路徑或jar。

2   類加載器的特點

1)運作一個程式時,總是由Application Loader(系統類加載器)開始加載指定的類。

2)在加載類時,每個類加載器會将加載任務上交給其父,如果其父找不到,再由自己去加載。

3)Bootstrap Loader(啟動類加載器)是最頂級的類加載器了,其父加載器為null.

3   類加載器的雙親委派模型

       類加載器雙親委派模型的工作過程是:如果一個類加載器收到一個類加載的請求,它首先将這個請求委派給父類加載器去完成,每一個層次類加載器都是如此,則所有的類加載請求都會傳送到頂層的啟動類加載器,隻有父加載器無法完成這個加載請求(即它的搜尋範圍中沒有找到所要的類),子類才嘗試加載。

      使用雙親委派模型主要是兩個原因:1)可以避免重複加載,當父類已經加載了,則就子類不需再次加載;2)安全因素,如果不用這種,則使用者可以随意的自定義加載器來替代Java核心API,則就會帶來安全隐患。

       下面是一個類加載器雙親委派模型,這裡各個類加載器并不是繼承關系,它們利用組合實作的父類與子類關系。

<a target="_blank" href="http://blog.51cto.com/attachment/201306/100356267.jpg"></a>

4 類加載的幾種方式

1) 指令行啟動應用時候由JVM初始化加載,加載含有main的主類。

      2)通過Class.forName("Hello")方法動态加載類,預設會執行初始化塊,這是因為Class.forName("Hello")其實就是Class.forName("Hello",true,CALLCLASS.getClassLoader()),第二個參數就是類加載過程中的連接配接操作。如果指定了ClassLoader,則不會執行初始化塊。

      3)通過ClassLoader.loadClass("Hello")方法動态加載類,不會執行初始化塊,因為loadClass方法有兩個參數,使用者隻是用第一個參數,第二個參數預設為false,即不對該類進行解析則就不會初始化。

5 類加載執行個體

當在指令行下執行:java HelloWorld(HelloWorld是含有main方法的類的Class檔案),JVM會将HelloWorld.class加載到記憶體中,并在堆中形成一個Class的對象HelloWorld.class。

      基本的加載流程如下:

      1)尋找jre目錄,尋找jvm.dll,并初始化JVM;

      2)産生一個Bootstrap Loader(啟動類加載器);

      3)Bootstrap Loader,該加載器會加載它指定路徑下的Java核心API,并且再自動加載Extended Loader(标準擴充類加載器),Extended Loader會加載指定路徑下的擴充JavaAPI,并将其父Loader設為BootstrapLoader。

      4)Bootstrap Loader也會同時自動加載AppClass Loader(系統類加載器),并将其父Loader設為ExtendedLoader。

     5)最後由AppClass Loader加載CLASSPATH目錄下定義的類,HelloWorld類。

本文轉自 zhao_xiao_long 51CTO部落格,原文連結:http://blog.51cto.com/computerdragon/1223354