目的:保證Class流的格式是正確的html
文件格式的驗證java
元數據驗證設計模式
字節碼驗證 (很複雜)數組
符號引用驗證安全
分配內存,併爲類設置初始值 (方法區中,關於方法區請查看Java內存區域)網絡
解析是惟一一個不肯定順序的過程,它在某些狀況下能夠在初始化階段以後開始,這是爲了支持 Java 語言的運行時綁定(也成爲動態綁定或晚期綁定)。另外注意這裏的幾個階段是按順序開始,而不是按順序進行或完成,由於這些階段一般都是互相交叉地混合進行的,一般在一個階段執行的過程當中調用或激活另外一個階段。數據結構
這裏簡要說明下 Java 中的綁定:綁定指的是把一個方法的調用與方法所在的類(方法主體)關聯起來,對 Java 來講,綁定分爲靜態綁定和動態綁定:app
符號引用替換爲直接引用框架
符號引用只是一種表示的方式,好比某個類繼承java.lang.object,在符號引用階段,只會記錄該類是繼承"java.lang.object",以這種字符串的形式保存,可是不能保證該對象被記載。ide
直接引用就是真正能使用的引用,它是指針或者地址偏移量,引用對象必定在內存。最終知道在內存中到底放在哪裏。
替換後,Class才能索引到它要用的那些內容。
一、類或接口的解析:判斷所要轉化成的直接引用是對數組類型,仍是普通的對象類型的引用,從而進行不一樣的解析。
二、字段解析:對字段進行解析時,會先在本類中查找是否包含有簡單名稱和字段描述符都與目標相匹配的字段,若是有,則查找結束;若是沒有,則會按照繼承關係從上往下遞歸搜索該類所實現的各個接口和它們的父接口,尚未,則按照繼承關係從上往下遞歸搜索其父類,直至查找結束,查找流程以下圖所示:
從下面一段代碼的執行結果中很容易看出來字段解析的搜索順序:
class Super{ public static int m = 11; static{ System.out.println("執行了super類靜態語句塊"); } } class Father extends Super{ public static int m = 33; static{ System.out.println("執行了父類靜態語句塊"); } } class Child extends Father{ static{ System.out.println("執行了子類靜態語句塊"); } } public class StaticTest{ public static void main(String[] args){ System.out.println(Child.m); } }
執行結果以下:
執行了super類靜態語句塊 執行了父類靜態語句塊 33
若是註釋掉 Father 類中對 m 定義的那一行,則輸出結果以下:
執行了super類靜態語句塊 11
static變量發生在靜態解析階段,也便是初始化以前,此時已經將字段的符號引用轉化爲了內存引用,也便將它與對應的類關聯在了一塊兒,因爲在子類中沒有查找到與 m 相匹配的字段,那麼 m 便不會與子類關聯在一塊兒,所以並不會觸發子類的初始化。
至於爲何先執行super的類靜態語句塊,後執行父類靜態語句塊是由於在初始化階段子類的<clinit>調用前保證父類的<clinit>被調用
最後須要注意:理論上是按照上述順序進行搜索解析,但在實際應用中,虛擬機的編譯器實現可能要比上述規範要求的更嚴格一些。若是有一個同名字段同時出如今該類的接口和父類中,或同時在本身或父類的接口中出現,編譯器可能會拒絕編譯。若是對上面的代碼作些修改,將 Super 改成接口,並將 Child 類繼承 Father 類且實現 Super 接口,那麼在編譯時會報出以下錯誤:
Exception in thread "main" java.lang.Error: Unresolved compilation problem: The field Child.m is ambiguous at StaticTest.main(StaticTest.java:20)
三、類方法解析:對類方法的解析與對字段解析的搜索步驟差很少,只是多了判斷該方法所處的是類仍是接口的步驟,並且對類方法的匹配搜索,是先搜索父類,再搜索接口。
四、接口方法解析:與類方法解析步驟相似,只是接口不會有父類,所以,只遞歸向上搜索父接口就好了。
執行類構造器<clinit>
子類的<clinit>調用前保證父類的<clinit>被調用
所以,在虛擬機中第一個被執行的()方法的類確定是java.lang.Object
接口中不能使用靜態語句塊,但仍然有類變量(final static)初始化的賦值操做,所以接口與類同樣會生成()方法。可是接口與類不一樣的是:執行接口的()方法不須要先執行父接口的()方法,只有當父接口中定義的變量被使用時,父接口才會被初始化。另外,接口的實現類在初始化時也同樣不會執行接口的()方法。
實驗:
package test; public class Test extends A { static { System.out.println("Test"); } public static void main(String[] args) { A a = new A(); } } class A { static { System.out.println("A"); } }
輸出:
A Test
<clinit>是線程安全的
那麼Java.lang.NoSuchFieldError錯誤可能在什麼階段拋出呢?
很顯然是在連接的驗證階段的符號引用驗證時會拋出這個異常,或者NoSuchMethodError等異常。
public Class<?> loadClass(String name) throws ClassNotFoundException
載入並返回一個Class
protected final Class<?> defineClass(byte[] b, int off, int len)
定義一個類,不公開調用
protected Class<?> findClass(String name) throws ClassNotFoundException
loadClass回調該方法,自定義ClassLoader的推薦作法
protected final Class<?> findLoadedClass(String name)
尋找已經加載的類
每一個ClassLoader都有一個Parent做爲父親( BootStrap除外)
自底向上檢查類是否被加載,通常狀況下,首先先從AppClassLoader中調用findLoadedClass查看是否已經加載,若是沒有加載,則會交給父類,ExtensionClassLoader去查看是否加載,還沒加載,則再調用其父類,BootstrapClassLoader查看是否已經加載,若是仍然沒有,自頂向下嘗試加載類,那麼從 BootstrapClassLoader到 AppClassLoader依次嘗試加載。
即便兩個類來源於同一個 Class 文件,只要加載它們的類加載器不一樣,那這兩個類就一定不相等。這裏的「相等」包括了表明類的 Class 對象的 equals()、isAssignableFrom()、isInstance()等方法的返回結果,也包括了使用 instanceof 關鍵字對對象所屬關係的斷定結果。
從代碼上能夠很容易看出來,首先先本身查看這個類是否被調用,若是沒有找到,則調用父親的loadClass,直到BootstrapClassLoader(沒有父親)。
咱們把這個加載的過程叫作雙親模式。
雙親委託機制的做用是防止系統jar包被本地替換
這種模式下會有一個問題:
頂層ClassLoader,沒法加載底層ClassLoader的類
Java框架(rt.jar)如何加載應用的類?
好比:javax.xml.parsers包中定義了xml解析的類接口
Service Provider Interface SPI 位於rt.jar
即接口在啓動ClassLoader中。
而SPI的實現類,在AppLoader。
這樣就沒法用BootstrapClassLoader去加載SPI的實現類。
JDK中提供了一個方法:
Thread. setContextClassLoader()
上下文ClassLoader能夠突破雙親模式的侷限性
1. http://www.cnblogs.com/snake-hand/p/3151381.html
2. http://www.cnblogs.com/Lawson/archive/2012/07/31/2616623.html
3. http://wiki.jikexueyuan.com/project/java-vm/class-loading-mechanism.html