Java語言是一種編譯後再通過解釋器執行的過程, 解釋器主要就是如何處理解釋Class文件的二進制字節流。JVM主要包含三大核心部分:運行時數據區,類加載器和執行引擎。java
虛擬機將描述類的數據從Class文件加載到內存,並對數據進行校驗、準備、解析和初始化,最終就會造成能夠被虛擬機使用的Java類型,這就是一個虛擬機的類加載機制。Java中的類是動態加載的,只有在運行期間使用到該類的時候,纔會將該類加載到內存中,Java依賴於運行期動態加載和動態連接來實現類的動態使用。數組
一個類的整個生命週期以下:安全
加載,驗證,準備,初始化和卸載在開始的順序上是固定的,可是能夠交叉進行。數據結構
在Java中,對於類有且僅有四種狀況會對類進行「初始化」。jvm
1) 使用new關鍵字實例化對象的時候,讀取或設置一個類的靜態字段時候(除final修飾的static外),調用類的靜態方法時候,都只會初始化該靜態字段或者靜態方法所定義的類。spa
2) 使用reflect包對類進行放射調用的時候,若是類沒有進行初始化,則先要初始化該類命令行
3) 當初始化一個類的時候,若是其父類沒有初始化過,則先要觸發其父類初始化。指針
4) 虛擬機啓動的時候,會初始化一個有main方法的主類。code
注意:經過子類引用父類靜態字段,只會初始化父類不會初始化子類;經過數組定義來引用類,也不會觸發該類的初始化;常量在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類,所以也不會觸發定義常量的類的初始化。對象
加載階段主要完成三件事,即經過一個類的全限定名來獲取定義此類的二進制字節流,將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構,在Java堆中生成一個表明此類的Class對象,做爲訪問方法區這些數據的入口。這個加載過程主要就是靠類加載器實現的,這個過程能夠由用戶自定義類的加載過程。
這個階段目的在於確保Class文件的字節流中包含信息符合當前虛擬機要求,不會危害虛擬機自身安全。主要包括四種驗證:
文件格式驗證:基於字節流驗證,驗證字節流是否符合Class文件格式的規範,而且能被當前虛擬機處理。
元數據驗證:基於方法區的存儲結構驗證,對字節碼描述信息進行語義驗證。
字節碼驗證:基於方法區的存儲結構驗證,進行數據流和控制流的驗證。
符號引用驗證:基於方法區的存儲結構驗證,發生在解析中,是否能夠將符號引用成功解析爲直接引用。
僅僅爲類變量(即static修飾的字段變量)分配內存而且設置該類變量的初始值即零值,這裏不包含用final修飾的static,由於final在編譯的時候就會分配了,同時這裏也不會爲實例變量分配初始化。類變量會分配在方法區中,而實例變量是會隨着對象一塊兒分配到Java堆中。
解析主要就是將常量池中的符號引用替換爲直接引用的過程。符號引用就是一組符號來描述目標,能夠是任何字面量,而直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。有類或接口的解析,字段解析,類方法解析,接口方法解析。
這裏要注意若是有一個同名字段同時出如今一個類的接口和父類中,那麼編譯器通常都會拒絕編譯。
初始化階段依舊是初始化類變量和其餘資源,這裏將執行用戶的static字段和靜態語句塊的賦值操做。這個過程就是執行類構造器<clinit>方法的過程。
<clinit>方法是由編譯器收集類中全部類變量的賦值動做和靜態語句塊的語句生成的,類構造器<clinit>方法與實例構造器<init>方法不一樣,這裏面不用顯示的調用父類的<clinit>方法,父類的<clinit>方法會自動先執行於子類的<clinit>方法。即父類定義的靜態語句塊和靜態字段都要優先子類的變量賦值操做。
啓動類加載器(Bootstrap ClassLoader): 主要負責加載<JAVA_HOME>\lib目錄中的,或是-Xbootclasspath參數指定的路徑中的,而且能夠被虛擬機識別(僅僅按照文件名識別的)的類庫到虛擬機內存中。它加載的是System.getProperty("sun.boot.class.path")所指定的路徑或jar。
擴展類加載器(Extension ClassLoader):主要負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的全部類庫。它加載的是
System.getProperty("java.ext.dirs")所指定的路徑或jar。
應用程序類加載器(Application ClassLoader):也叫系統類加載器,主要負責加載ClassPath路徑上的類庫,若是應用程序沒有自定義本身類加載器,則這個就是默認的類加載器。
它加載的是System.getProperty("java.class.path")所指定的路徑或jar。
1)運行一個程序時,老是由Application Loader(系統類加載器)開始加載指定的類。
2)在加載類時,每一個類加載器會將加載任務上交給其父,若是其父找不到,再由本身去加載。
3)Bootstrap Loader(啓動類加載器)是最頂級的類加載器了,其父加載器爲null.
類加載器雙親委派模型的工做過程是:若是一個類加載器收到一個類加載的請求,它首先將這個請求委派給父類加載器去完成,每個層次類加載器都是如此,則全部的類加載請求都會傳送到頂層的啓動類加載器,只有父加載器沒法完成這個加載請求(即它的搜索範圍中沒有找到所要的類),子類才嘗試加載。
使用雙親委派模型主要是兩個緣由:1)能夠避免重複加載,當父類已經加載了,則就子類不需再次加載;2)安全因素,若是不用這種,則用戶能夠隨意的自定義加載器來替代Java核心API,則就會帶來安全隱患。
下面是一個類加載器雙親委派模型,這裏各個類加載器並非繼承關係,它們利用組合實現的父類與子類關係。
1) 命令行啓動應用時候由JVM初始化加載,加載含有main的主類。
2)經過Class.forName("Hello")方法動態加載類,默認會執行初始化塊,這是由於Class.forName("Hello")其實就是Class.forName("Hello",true,CALLCLASS.getClassLoader()),第二個參數就是類加載過程當中的鏈接操做。若是指定了ClassLoader,則不會執行初始化塊。
3)經過ClassLoader.loadClass("Hello")方法動態加載類,不會執行初始化塊,由於loadClass方法有兩個參數,用戶只是用第一個參數,第二個參數默認爲false,即不對該類進行解析則就不會初始化。
當在命令行下執行: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類。
原文地址:http://computerdragon.blog.51cto.com/6235984/1223354