前面又說到Java程序其實是將。class文件放入JVM中運行。虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗,轉換,解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是JVM的類加載機制java
類從加載虛擬機內存中開始到卸載出內存爲止,生命週期包括:加載、驗證、準備、解析、初始化、使用、卸載。程序員
一、加載數組
經過一個類的全限定名來獲取定義此類的二進制字節流(沒有指明二進制字節流要從一個Class文件中獲取,能夠從ZIP包中讀取,從網絡中獲取,運行時計算生成等等)將這個字節流所表明的靜態儲存結構轉化爲方法區的運行時數據結構在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口完成後,虛擬機外部的二進制字節流就按照虛擬機所需格式儲存在方法區中。這裏稍微理解一下對象和類的概念,對象是實例化的類。類的信息是存儲在方法區中的,對象是存儲在Java堆中的。類是對象的模板,對象是類的實例。這裏主要講類。網絡
二、驗證數據結構
** 加載階段未完成,鏈接階段已經開始。二者之間會交叉運行。**由於Class文件能夠用任何途徑產生,字節流不符合Class文件格式的約束,虛擬機會拋出java.lang.VerifyError異常,因此爲了保護虛擬機,JVM會有如下四個方面的驗證多線程
文件格式驗證:即驗證類文件結構佈局
元數據驗證:這個是否有父類,父類是否繼承了不容許被繼承的類等等 線程
字節碼驗證:對類的方法體進行校驗,JDK1.6後只需檢查StackMapTable屬性中的記錄是否合法,JDK1.7後對於主版本號大於50的Class文件,使用類型檢查來完成數據流分析翻譯
符號引用驗證:全限定名是否能找到對應的類,在指定類中是否存在符合方法的字段描述以及簡單名稱描述的方法,字段。訪問性是否正確。驗證不成功會拋出java.lang.incompatibleClassChangeError異常的子類。指針
三、準備
正式爲類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。這個時候進行內存分配的只包括類變量(被static修飾的變量),並不包括實例變量,實例變量是在對象實例化時隨對象一塊兒分配在java堆中。這時候分配的初始值爲零值,假設一個變量定義爲:public static int value = 123;則設置變量的初始值應該爲0, 而不是123。 把value賦值爲123的putstatic指令是在程序被編譯後,存放於類構造器()方法之中,因此把value賦值爲123的動做將在初始化階段纔會執行。若是字段屬性表中存在ConstantValue屬性,那麼在準備階段會將value賦值爲ConstantValue屬性所指定的值 。例如:public static final int value = 123; 那麼在準備階段,則會將value賦值爲123;
四、解析
將符號引用轉換爲直接引用的過程。符號引用是一組以符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用是能無歧義的定位到目標便可。符號引用與虛擬機實現的內存佈局無關,引用的目標並不必定已經加載到內存中。而直接引用能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的內存佈局相關的,同一個符號引用在不一樣虛擬機實例上翻譯出來的直接引用通常不會相同。若是有了直接引用,那引用的目標一定已經在內存中存在。
解析動做主要針對:類或接口、字段(類成員變量)、類方法、接口方法等引用進行。
類或接口的解析:判斷所要轉化成的直接引用是對數組類型,仍是對普通的對象類型的引用,從而進行不一樣的解析。
字段解析:對字段進行解析時,會先在本類中查找是否包含有簡單名稱和字段描述符都與目標相匹配的字段,若是有,則查找結束;若是沒有,則會按照繼承關係從上往下遞歸搜索該類所實現的各個接口和它們的父接口,尚未,則按照繼承關係從上往下遞歸搜索其父類,直至查找結束,查找流程以下圖所示:
最後須要注意:理論上是按照上述順序進行搜索解析,但在實際應用中,虛擬機的編譯器實現可能要比上述規範要求的更嚴格一些。若是有一個同名字段同時出如今該類的接口和父類中,或同時在本身或父類的接口中出現,編譯器可能會拒絕編譯。
類方法解析:對類方法的解析與對字段解析的搜索步驟差很少,只是多了判斷該方法所處的是類仍是接口的步驟,並且對類方法的匹配搜索,是先搜索父類,再搜索接口。
接口方法解析:與類方法解析步驟相似,因爲接口不會有父類,所以,只遞歸向上搜索父接口就好了。
五、初始化
是類加載過程的最後一步,到了此階段,才真正開始執行類中定義的Java程序代碼。在準備階段,類變量已經被賦過一次系統要求的初始值,而在初始化階段,則是根據程序員經過程序指定的主觀計劃去初始化類變量和其餘資源。到初始化階段,才真正開始執行類中的Java程序代碼。即初始化階段是執行類構造器()方法的過程。
()方法解析過程:方法是有編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static{}塊)中的語句合併產生的,編譯器的順序是由語句在源文件中出現的順序決定的,因此靜態語句塊中只能訪問到定義在靜態語句塊以前的變量,定義在它以後的變量,在前面的靜態語句塊能夠賦值,但不能訪問。虛擬機會保證在子類的()方法執行前,父類的()方法已經執行完畢,所以在虛擬機中的一個被執行的()方法的類確定是java。lang。Object。()方法對於類或接口來講並非必需的,若是一個類中沒有靜態語句塊,也沒有對變量的賦值操做,那麼編譯器能夠不爲這個類生成()方法。因此這個()方法主要是給靜態變量賦值和執行靜態語句塊。接口中不能使用靜態語句塊,但仍然有變量初始化的賦值操做,所以接口與類同樣都會生成()方法,但接口與類不一樣的是,執行接口的()方法不須要先執行父接口的()方法。只有當父接口中定義的變量使用時,父接口才會初始化。另外,接口的實現類在初始化時也同樣不會執行接口的()方法。
虛擬機會保證一個類的()方法在多線程環境中被正確的加鎖、同步,若是多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的()方法,其餘線程都須要阻塞等待,直到活動線程執行()方法完畢。若是在一個類的()方法中有耗時很長的操做,就可能會形成多個進程阻塞,在實際應用中這種阻塞每每是很隱蔽的。
小結:
目前來講也就是加載階段獲取類的信息,驗證階段驗證類是否符合JVM的標準。這兩個階段能夠交叉運行。而後就須要在準備階段給類分配內存空間,設置常量的初始值,初始化靜態變量等等。解析階段就是將符號引用轉換爲直接引用,讓類在JVM上有對應的內存。最後是初始化階段就是執行類中的靜態變量賦值語句和靜態語句塊。到這裏的類一些基礎的工做已經作好了,可使用了。
實現「經過一個類的權限定名來獲取描述此類的二進制字節流」這個動做的代碼模塊稱爲類加載器java類的加載是由虛擬機來完成的,虛擬機把描述類的Class文件加載到內存,並對數據進行校驗,解析和初始化,最終造成能被java虛擬機直接使用的java類型,這就是虛擬機的類加載機制。JVM中用來完成上述功能的具體實現就是類加載器。類加載器讀取。class字節碼文件將其轉換成java。lang。Class類的一個實例。每一個實例用來表示一個java類。經過該實例的newInstance()方法能夠建立出一個該類的對象。
一、引導類加載器(Bootstrap ClassLoader)
這個類加載器負責將<JAVA_HOME>\lib目錄下的類庫加載到虛擬機內存中,用來加載java的核心庫,此類加載器並不繼承於java.lang.ClassLoader,不能被java程序直接調用,代碼是使用C++編寫的。是虛擬機自身的一部分
二、擴展類加載器(Extendsion ClassLoader)
這個類加載器負責加載<JAVA_HOME>\lib\ext目錄下的類庫,用來加載java的擴展庫,開發者能夠直接使用這個類加載器。
三、應用程序類加載器(Application ClassLoader)
這個類加載器負責加載用戶類路徑(CLASSPATH)下的類庫,通常咱們編寫的java類都是由這個類加載器加載,這個類加載器是CLassLoader中的getSystemClassLoader()方法的返回值,因此也稱爲系統類加載器。通常狀況下這就是系統默認的類加載器
四、自定義的類加載器
這個加載器能夠知足咱們加載類的特殊需求,須要繼承java.lang.ClassLoader類而且覆蓋其中的findClass()方法和defineClass()方法。
五、雙親委派機制
上面的4個加載器並非並行加載的,JVM中是經過雙親委派機制來加載的:
雙親委派模型是一種組織類加載器之間關係的一種規範,它的工做原理是:若是一個類加載器收到了類加載的請求,它不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,這樣層層遞進,最終全部的加載請求都被傳到最頂層的啓動類加載器中,只有當父類加載器沒法完成這個加載請求(它的搜索範圍內沒有找到所需的類)時,纔會交給子類加載器去嘗試加載。這樣的好處是:java類隨着它的類加載器一塊兒具有了帶有優先級的層次關係。這是十分必要的,好比java.lang.Object,它存放在\jre\lib\rt。jar中,它是全部java類的父類,所以不管哪一個類加載都要加載這個類,最終全部的加載請求都彙總到頂層的啓動類加載器中。Object類會由啓動類加載器來加載,因此加載的都是同一個類,若是不使用雙親委派模型,由各個類加載器自行去加載的話,系統中就會出現不止一個Object類,應用程序就會全亂了。
一、遇到new,getstatic,putstatic,invokestatic字節碼指令。最多見的Java代碼場景是:使用new實例化對象、讀取或設置一個類的靜態字段(被final修飾或已在編譯器把結果放入常量池的靜態字段除外)、調用一個類的靜態方法
二、使用java.lang.reflect包的方法對類進行反射調用
三、類繼承了父類,父類要先初始化
四、虛擬機啓動,初始化主類
五、當使用JDK 1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。