C++面向物件基礎一
JVM類載入機制
類載入機制是指.class檔案載入到JVM,並形成Class物件的機制,之後應用就可對Class物件進行例項化並呼叫,類載入機制可在執行時動態載入外部的類、遠端網路下載過來的class檔案等。除了該動態化的優點外,還可通過JVM的類載入機制來達到類隔離的效果,例如Application Server中通常要避免兩個應用的類互相干擾。
JVM將類載入過程劃分為三個步驟:裝載、連結和初始化。裝載和連結過程完成後,即將二進位制的位元組碼轉換為Class物件;初始化過程不是載入類時必須觸發的,但最遲必須在初次主動使用物件前執行,其所作的動作為給靜態變數賦值、呼叫()等。
整個過程如圖3.3所示。
裝載(Load)->連結(Link)[校驗(Verify)->準備(Prepare)->解析(Resolve)]->初始化(Initialize)
1. 裝載(Load)
裝載過程負責找到二進位制位元組碼並載入至JVM中,JVM通過類的全限定名(com.bluedavy. HelloWorld)及類載入器(ClassLoaderA例項)完成類的載入,同樣,也採用以上兩個元素來標識一個被載入了的類:類的全限定名+ClassLoader例項ID。類名的命名方式如下:
對於介面或非陣列型的類,其名稱即為類名,此種類型的類由所在的ClassLoader負責載入;
對於陣列型的類,其名稱為"["+(基本型別或L+引用型別類名;),例如byte[] bytes=new byte[512],該bytes的類名為:[B; Object[] bjects=new Object[10],objects的類名則為:[Ljava.lang.Object;,陣列型類中的元素型別由所在的ClassLoader負責載入,但陣列類則由JVM直接建立。
2. 連結(Link)
連結過程負責對二進位制位元組碼的格式進行校驗、初始化裝載類中的靜態變數及解析類中呼叫的介面、類。
二進位制位元組碼的格式校驗遵循Java Class File Format(具體請參見JVM規範)規範,如果格式不符合,則丟擲VerifyError;校驗過程中如果碰到要引用到其他的介面和類,也會進行載入;如果載入過程失敗,則會丟擲NoClassDefFoundError。
在完成了校驗後,JVM初始化類中的靜態變數,並將其值賦為預設值。
最後對類中的所有屬性、方法進行驗證,以確保其要呼叫的屬性、方法存在,以及具備相應的許可權(例如public、private域許可權等)。如果這個階段失敗,可能會造成NoSuchMethodError、NoSuchFieldError等錯誤資訊。
3. 初始化(Initialize)
初始化過程即執行類中的靜態初始化程式碼、構造器程式碼及靜態屬性的初始化,在以下四種情況下初始化過程會被觸發執行:
1)呼叫了new;
2)反射呼叫了類中的方法;
3)子類呼叫了初始化;
4)JVM啟動過程中指定的初始化類。
在執行初始化過程之前,首先必須完成連結過程中的校驗和準備階段,解析階段則不強制。
JVM的類載入通過ClassLoader及其子類來完成,分為Bootstrap ClassLoader、Extension ClassLoader、System ClassLoader及User-Defined ClassLoader。這4種ClassLoader的關係如圖3.4所示。
Bootstrap Class Loader----java_home/jre/lib/rt.jar
Extention Class Loader ----java_home/jre/lib/ext/*.jar
System Class Loader ----classpath
UserDefined Class Loader
1. Bootstrap ClassLoader
Sun JDK採用C++實現了此類,此類並非ClassLoader的子類,在程式碼中沒有辦法拿到這個物件,Sun JDK啟動時會初始化此ClassLoader,並由ClassLoader完成$JAVA_HOME中jre/lib/rt.jar裡所有class檔案的載入,jar中包含了Java規範定義的所有介面及實現。
2. Extension ClassLoader
JVM用此ClassLoader來載入擴充套件功能的一些jar包,例如Sun JDK中目錄下有dns工具jar包等,在Sun JDK中ClassLoader對應的類名為ExtClassLoader。
3. System ClassLoader
JVM用此ClassLoader來載入啟動引數中指定的Classpath中的jar包及目錄,在Sun JDK中ClassLoader對應的類名為AppClassLoader。
例如一段這樣的程式碼:
- public class ClassLoaderDemo {
- public static void main(String[] args) throws Exception{
- System.out.println(ClassLoaderDemo.class.getClassLoader());
- System.out.println(ClassLoaderDemo.class.getClassLoader().getParent());
- System.out.println(ClassLoaderDemo.class.
- getClassLoader().getParent().getParent());
- }
- }
執行後顯示的資訊類似如下:
- (sun.misc.Launcher$AppClassLoader)
- (sun.misc.Launcher$ExtClassLoader)
- null
按照上面的描述,就可看到典型的System ClassLoader、Extension ClassLoader,而由於Bootstrap ClassLoader並不是Java中的ClassLoader,因此Extension ClassLoader的parent為null。
4. User-Defined ClassLoader
User-Defined ClassLoader是Java開發人員繼承ClassLoader抽象類自行實現的ClassLoader,基於自定義的ClassLoader可用於載入非Classpath中(例如從網路上下載的jar或二進位制)的jar及目錄、還可以在載入之前對class檔案做一些動作,例如解密等。
JVM的ClassLoader採用的是樹形結構,除BootstrapClassLoader外,其他的ClassLoader都會有parent ClassLoader,User-Defined ClassLoader預設的parent ClassLoader為System ClassLoader。載入類時通常按照樹形結構的原則來進行,也就是說,首先應從parent ClassLoader中嘗試進行載入,當parent中無法載入時,應再嘗試從System ClassLoader中進行載入,System ClassLoader同樣遵循此原則,在找不到的情況下會自動從其parent ClassLoader中進行載入。值得注意的是,由於JVM是採用類名加Classloader的例項來作為Class載入的判斷的,因此載入時不採用上面的順序也是可以的,例如載入時不去parent ClassLoader中尋找,而只在當前的ClassLoader中尋找,會造成樹上多個不同的ClassLoader中都載入了某Class,並且這些Class的例項物件都不相同,JVM會保證同一個ClassLoader例項物件中只能載入一次同樣名稱的Class,因此可藉助此來實現類隔離的需求,但有時也會帶來困惑,例如ClassCastException。因此在載入類的順序上要根據需求合理把握,儘量保證從根到最下層的ClassLoader上的Class只加載了一次。
ClassLoader抽象類提供了幾個關鍵的方法:
loadClass
findLoadedClass
findClass
findSystemClass
defineClass
resolveClass
根據上面的描述,在實際的應用中,JVM類載入過程會丟擲這樣那樣的異常,這些情況下掌握各種異常產生的原因是最為重要的,下面來看類載入方面的常見異常。
1. ClassNotFoundException
2. NoClassDefFoundError
3. LinkageError
4. ClassCastException