類加載機制
類加載機制是指 .class
文件加載到JVM,并形成Class對象的機制。
類加載機制可以在運行時動態的加載外部的類、遠程網絡下載過來 class 文件等。除了動態的優點wai外,還可以通過JVM的類加載機制來達到類隔離的效果。
JVM將類加載劃分為三個步驟:
- 裝載
- 鏈接
- 初始化
裝載和鏈接完成后就將二進制的字節碼轉換為 Class 對象;而初始化過程并不是加載類時必須觸發的(為什么呢),但是最遲必須在初次主動使用對象前執行。
裝載
裝載過程負責找到二進制字節碼并加載至JVM中,JVM通過類的全限定名及類加載器完成類的加載,同樣,也采用全限定名及類加載器來標識一個被加載了的類:類的全限定名 + ClassLoader實例ID
。
類名的命名方式如下:
- 對于接口或非數組型的類,其名稱即為類名,此種類型的類由所在的ClassLoader負責加載;
- 對于數組型的類,其名稱為“[”+(基本類型|L)+引用類型類名;)
byte[] bytes = new byte[512];
System.out.println(bytes.getClass().getName());
Object[] objects = new Object[10];
System.out.println(objects.getClass().getName());
String[] strings = new String[10];
System.out.println(strings.getClass().getName());
# Output ---
[B
[Ljava.lang.Object;
[Ljava.lang.String;
鏈接
一:校驗 鏈接過程負責對二進制字節碼的格式進行校驗、初始化裝載類中的靜態變量及解析類中調用的接口和類。
校驗過程中如果碰到要引用到其他的接口和類,也會進行加載;如果加載過程失敗,則會拋出NoClassDefFoundError。
二:準備 在完成了校驗后,JVM初始化類中的靜態變量,并將其值賦為默認值。
三:解析 最后對類中的所有屬性、方法進行驗證,以確保其要調用的屬性、方法存在,以及具備相應的權限(例如public、private域權限等)。如果這個階段失敗,可能會造成NoSuchMethodEr-ror、NoSuchFieldError等錯誤信息。
初始化
初始化過程即執行類中的靜態初始化代碼、構造器代碼及靜態屬性的初始化。
以下四種情況下初始化過程會被觸發執行:
- 調用了new;
- 反射調用了類中的方法;
- 子類調用了初始化;
- JVM啟動過程中指定的初始化類。
在執行初始化過程之前,首先必須完成鏈接過程中的校驗和準備階段,解析階段則不強制。
JVM的類加載通過ClassLoader及其子類來完成:
- Bootstrap ClassLoader 在代碼中沒有辦法拿到這個對象,Sun JDK啟動時會初始化此ClassLoader,并由ClassLoader加載$JAVA_HOME/jre/lib/rt.jar里所有class文件;
- Extension ClassLoader JVM用此ClassLoader來加載擴展功能的一些jar包 $JAVA_HOME/jre/lib/ext/*.jar;
- System ClassLoader(AppClassLoader) JVM用此ClassLoader來加載啟動參數中指定的Classpath中的jar包及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader;
- UserDefined ClassLoader 繼承Class-Loader抽象類自行實現的ClassLoader,基于自定義的ClassLoader可用于加載非Classpath中(例如從網絡上下載的jar或二進制)的jar及目錄、還可以在加載之前對class文件做一些動作(例如解密等)。
public class ClassTest {
@Test
public void testClassLoader() {
System.out.println(ClassTest.class.getClassLoader());
System.out.println(ClassTest.class.getClassLoader().getParent());
System.out.println(ClassTest.class.getClassLoader().getParent().getParent());
}
}
# Output ---
sun.misc.Launcher$AppClassLoader@3836b1bb
sun.misc.Launcher$ExtClassLoader@ece88d2
null
JVM的ClassLoader采用的是樹形結構,除BootstrapClass-Loader外,其他的ClassLoader都會有parent ClassLoader,UserDefined ClassLoader默認的parent ClassLoader為System ClassLoader。加載類時通常按照樹形結構的原則來進行,也就是說,首先應從 UserDefined ClassLoader中嘗試進行加載,當par-ent中無法加載時,應再嘗試從System ClassLoader中進行加載,System ClassLoader同樣遵循此原則,在找不到的情況下會自動從其parent ClassLoader中進行加載。
JVM是采用
類名+Classloader的實例
來作為Class加載的判斷的,因此加載時不采用上面的順序也是可以的,例如加載時不去parent ClassLoader中尋找,而只在當前的ClassLoader中尋找,會造成樹上多個不同的ClassLoader中都加載了某Class,并且這些Class的實例對象都不相同。
當Java開發人員調用Class.forName來獲取一個對應名稱的Class對象時,JVM會從方法棧上尋找第一個ClassLoader,通常也就是執行Class.forName所在類的ClassLoader,并使用此ClassLoader來加載此名稱的類。
ClassNotFoundException 這是最常見的異常,產生這個異常的原因為在當前的ClassLoader中加載類時未找到類文件,對位于System ClassLoader的類很容易判斷,只要加載的類不在Classpath中。而對位于UserDefined ClassLoader的類則麻煩些,要具體查看這個ClassLoader加載類的過程,才能判斷此ClassLoader要從什么位置加載到此類。例如直接在代碼中執行Class.forName(“com.blue-davy.A”),而當前類的classloader下根本就沒有該類所在的jar或沒有該class文件,就會拋出ClassNotFoundException。
NoClassDefFoundError 該異常較之ClassNotFoundException更難處理一些,造成此異常的主要原因是加載的類中引用到的另外的類不存在,如下:要加載A,而A中調用了B,B不存在或當前ClassLoader沒法加載B,就會拋出這個異常。當采用Class.forName加載A時,雖能找到A.class,但此時B.class不存在,則會拋出NoClassDefFoundError。
public class A {
private B b=new B();
}
ClassCastException 這個異常比較難查的是兩個A對象由不同的ClassLoader加載的情況,這時如果將其中某個A對象造型成另外一個A對象,也會報出ClassCastException。