Java語言是一種編譯后再經過解釋器執行的過程, 解釋器主要就是如何處理解釋Class文件的二進制字節流。JVM主要包含三大核心部分:運行時數據區,類加載器和執行引擎。
虛擬機將描述類的數據從Class文件加載到內存,并對數據進行校驗、準備、解析和初始化,最終就會形成可以被虛擬機使用的Java類型,這就是一個虛擬機的類加載機制。Java中的類是動態加載的,只有在運行期間使用到該類的時候,才會將該類加載到內存中,Java依賴于運行期動態加載和動態鏈接來實現類的動態使用。
一個類的整個生命周期如下:
加載,驗證,準備,初始化和卸載在開始的順序上是固定的,但是可以交叉進行。
在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字段和靜態語句塊的賦值操作。這個過程就是執行類構造器方法的過程。
方法是由編譯器收集類中所有類變量的賦值動作和靜態語句塊的語句生成的,類構造器方法與實例構造器方法不同,這里面不用顯示的調用父類的方法,父類的方法會自動先執行于子類的方法。即父類定義的靜態語句塊和靜態字段都要優先子類的變量賦值操作。
二、類加載器
1、類加載器的分類
啟動類加載器(Bootstrap ClassLoader): 主要負責加載lib目錄中的,或是-Xbootclasspath參數指定的路徑中的,并且可以被虛擬機識別(僅僅按照文件名識別的)的類庫到虛擬機內存中。它加載的是System.getProperty("sun.boot.class.path")所指定的路徑或jar。
擴展類類加載器(Extension ClassLoader):主要負責加載libext目錄中的,或者被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、類加載器的雙親委派模型
類加載器雙親委派模型的工作過程是:如果一個類加載器收到一個類加載的請求,它首先將這個請求委派給父類加載器去完成,每一個層次類加載器都是如此,則所有的類加載請求都會傳送到頂層的啟動類加載器,只有父加載器無法完成這個加載請求(即它的搜索范圍中沒有找到所要的類),子類才嘗試加載。
下面是一個類加載器雙親委派模型,這里各個類加載器并不是繼承關系,它們利用組合實現的父類與子類關系。
4、類加載的幾種方式
1)命令行啟動應用時候由JVM初始化加載,加載含有main的主類。
2)通過Class.forName()方法動態加載類,默認會執行初始化塊。如果指定了ClassLoader,則不會執行初始化塊。
3)通過ClassLoader.loadClass()方法動態加載類,不會執行初始化塊。
5、類加載實例
當在命令行下執行:java HelloWorld(HelloWorld是含有main方法的類的Class文件),JVM會將HelloWorld.class加載到內存中,并在堆中形成一個Class的對象HelloWorld.class。
基本的加載流程如下:
1)尋找jre目錄,尋找jvm.dll,并初始化JVM;
2)產生一個Bootstrap Loader(啟動類加載器);
3)Bootstrap Loader自動加載Extended Loader(標準擴展類加載器),并將其父Loader設為BootstrapLoader。
4)Bootstrap Loader自動加載AppClass Loader(系統類加載器),并將其父Loader設為ExtendedLoader。
5)最后由AppClass Loader加載HelloWorld類。