虛擬機把描述類的數據從Class文件加載到內存,而且對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
編譯時無需進行鏈接工做,類的加載、鏈接和初始化過程都是在程序運行期間完成的。如面向接口的應用程序能夠等到運行時再指定其實際的實現類;用戶能夠經過預約義或者自定義的類加載器,讓本地的應用程序能夠在運行時從網絡或者其餘地方加載一個二進制流做爲程序代碼的一部分。數組
類從被加載到虛擬機內存中開始,到卸載出內存爲止,生命週期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。安全
5種狀況必須當即對類進行初始化:網絡
過程主要包括加載、驗證、準備、解析、初始化這5個階段數據結構
加載階段,虛擬機主要完成下面3件事情:函數
經過類的全限定名獲取類的二進制字節流有多種方式,如:佈局
非數組類可使用系統提供的引導類加載器完成,也能夠用戶自定義類加載器(重寫類加載器的loadClass()方法)
數組類不經過類加載器建立,由虛擬機直接建立。spa
驗證是鏈接階段的第一步,這一階段的目的是爲了確保Class文件的字節流種包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。線程
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區種進行分配。翻譯
解析階段是虛擬機將常量池內的符號引用替換位直接引用的過程,符號引用在Class文件種以CONSTSNT_Class_info、CONSTSNT_Fieldref_info、CONSTSNT_Methodref_info等類型的常量出現。
符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義的定位到目標便可。
直接引用(Direct References):直接引用能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的內存佈局相關的,同一個符號引用在不一樣虛擬機實例上翻譯出來的直接引用通常不會相同,若是有直接引用,那引用的目標一定已經在內存中存在。
類或接口的解析
假設類爲D,要把一個從未解析過的符號引用N解析爲一個類或者接口C的直接引用,解析過程分爲三步:
1)C不是數組類型,虛擬機把N的全限定名傳遞給D的類加載器去加載類C,過程當中一旦出現異常,解析宣告失敗。
2)C是數組類型且元素類型是對象,按照1的規則加載數組元素類型,接着由虛擬機生成一個表明此數組維度和元素的數組對象。
3)上面兩步沒有任何異常,C在虛擬機中已經成爲一個有效的類或者接口,可是解析完成前還要進行符號引用驗證,確認D是否具有對於C的訪問權限。
字段解析
解析字段符號引用,首先對字段表內class_index中索引的CONSTANT_Class_info符號引用進行解析,就是類或者接口符號引用,若是過程當中發生異常會致使解析失敗,若是成功將這個字段所屬的類或者接口以C表示,後續操做爲:
1)C自己包含簡單名稱和字段描述符都與目標匹配的字段,返回這個字段直接引用查找結束
2)若是C實現了接口,會按照繼承關係從下往上查找,若是找到直接引用查找結束
3)若是C不是java.lang.Object,按照繼承關係從下往上查找,若是找到直接引用查找結束
4)都沒找到,拋出java.lang.NoSuchFieldError。若是找到還要判斷是否具備訪問權限
類方法解析
和字段解析類似,咱們假設方法所屬的類解析成功爲C,後續操做:
1)若是在類方法表中發現class_index中索引的C是個接口,就拋出java.lang.IncompatibleClassChangeError,也就是類的方法不能是一個接口方法或者抽象方法,是必須實現的。
2)若是第一步沒問題,就在類C中查找是否有簡單名稱和描述符都與目標匹配的方法,若是找到直接引用查找結束
3)不然在C的父類中遞歸查找,若是找到直接引用查找結束
4)不然在C的接口列表和父接口中遞歸查詢,若是找到說明C是一個抽象類,查找結束拋出java.lang.AbstractMethodError異常。
5)都沒有找到,拋出java.lang.NoSuchMethodError,若是找到還要判斷是否具備訪問權限
接口方法解析
和類方法類似,設定方法所屬接口解析成功爲C,後續操做:
1)若是發現class_index的索引C是一個類而不是接口,就拋出java.lang.IncompatibleClassChangeError
2)在接口C中直接查找,若是找到直接引用查找結束
3)在接口C的父接口中遞歸查找,直到java.lang.Object類,若是找到直接引用查找結束
4)都沒有找到,拋出java.lang.NoSuchMethodError,接口的方法默認都是public,所以不具備訪問權限問題
類初始化是類加載過程的最後一步,這裏開始執行類中定義的Java程序代碼(或者說字節碼)。初始化階段是執行類構造器
虛擬機把類加載階段中的「經過一個類的全限定名來獲取描述此類的二進制字節流」這個動做放到Java虛擬機外部去實現,讓應用程序字節決定如何去獲取所須要的類,實現這個動做的代碼模塊稱爲「類加載器」。
任意一個類都要由加載他的類加載器和這個類自己一同確立其在Java虛擬機中的惟一性,每個類加載器都擁有一個獨立的類名稱空間。兩個類是否相等,須要來源於同一個Class文件,被同一個虛擬機加載,同一個類加載器。
Java虛擬機只存在兩種不一樣的類加載器:一種是啓動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現,是虛擬機自身的一部分;另外一種就是其餘全部的類加載器,這些類加載器都由Java語言實現,獨立於虛擬機外部,而且所有都繼承自抽象類java.lang.ClassLoader。
根據更細緻的分法,絕大部分Java程序都會使用到如下3種系統提供的類加載器。
類加載器的雙親委派模式(Parents Delegation Model)是指除了頂層的啓動類加載器外,其他的類加載器都應當由本身的父類加載器,類加載器之間的父子關係通常不會以繼承(Inheritantce)的關係來實現,而是都使用組合(Composition)的關係來複用父加載器的代碼。
雙親委派模型的工做過程是:若是一個類加載器收到類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,全部的加載請求最終都傳送到頂層的啓動類加載器中,只有父加載器沒法完成加載請求(它的搜索範圍中沒有找到所須要的類),子加載器纔會嘗試本身去加載。
這樣Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係,例如java.lang.Object,它在rt.jar中,不管哪一個類加載器要加載這個類最終都是交給頂層啓動類加載器進行加載,這樣Object類在全部的加載器環境中都是同一個類。