類載入是Java程式啟動並執行第一步,研究類的載入有助於瞭解JVM執行過程,並指導開發人員採取更有效措施配合程式執行。 研究類載入機制的第二個目的是讓程式能動態控制類載入,比如熱部署等,提高程式的靈活性和適應性。
一、簡單過程 Java程式啟動並執行場所是記憶體,當在命令列下執行: java HelloWorld 命令的時候,JVM會將HelloWorld.class載入到記憶體中,並形成一個Class的對象HelloWorld.class。 其中的過程就是類載入過程: 1、尋找jre目錄,尋找jvm.dll,並初始化JVM; 2、產生一個Bootstrap Loader(啟動類載入器); 3、Bootstrap Loader自動載入Extended Loader(標準擴充類載入器),並將其父Loader設為Bootstrap Loader。 4、Bootstrap Loader自動載入AppClass Loader(系統類別載入器),並將其父Loader設為Extended Loader。 5、最後由AppClass Loader載入HelloWorld類。 以上就是類載入的最一般的過程。
二、類載入器各自搜尋的目錄
為了弄清楚這個問題,首先還要看看System類的API doc文檔。 1、Bootstrap Loader(啟動類載入器):載入System.getProperty("sun.boot.class.path")所指定的路徑或jar。 2、Extended Loader(標準擴充類載入器ExtClassLoader):載入System.getProperty("java.ext.dirs")所指定的路徑或jar。在使用Java運行程式時,也可以指定其搜尋路徑,例如:java -Djava.ext.dirs=d:/projects/testproj/classes HelloWorld 3、AppClass Loader(系統類別載入器AppClassLoader):載入System.getProperty("java.class.path")所指定的路徑或jar。在使用Java運行程式時,也可以加上-cp來覆蓋原有的Classpath設定,例如: java -cp ./lavasoft/classes HelloWorld ExtClassLoader和AppClassLoader在JVM啟動後,會在JVM中儲存一份,並且在程式運行中無法改變其搜尋路徑。如果想在運行時從其他搜尋路徑載入類,就要產生新的類載入器。 三、類載入器的特點 1、運行一個程式時,總是由AppClass Loader(系統類別載入器)開始載入指定的類。 2、在載入類時,每個類載入器會將載入任務上交給其父,如果其父找不到,再由自己去載入。
3、Bootstrap Loader(啟動類載入器)是最頂級的類載入器了,其父載入器為null. 四、類載入器的擷取 很容易,看下面例子 public class HelloWorld {
public static void main(String[] args) {
HelloWorld hello = new HelloWorld();
Class c = hello.getClass();
ClassLoader loader = c.getClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
}
} 列印結果: sun.misc.Launcher$AppClassLoader@19821f
sun.misc.Launcher$ExtClassLoader@addbf1
null
Process finished with exit code 0 從上面的結果可以看出,並沒有擷取到ExtClassLoader的父Loader,原因是Bootstrap Loader(啟動類載入器)是用C語言實現的,找不到一個確定的返回父Loader的方式,於是就返回null。 五、類的載入 類載入有三種方式: 1、命令列啟動應用時候由JVM初始化載入 2、通過Class.forName()方法動態載入 3、通過ClassLoader.loadClass()方法動態載入 三種方式區別比較大,看個例子就明白了: public class HelloWorld {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = HelloWorld. class.getClassLoader();
System.out.println(loader);
//使用ClassLoader.loadClass()來載入類,不會執行初始化塊
loader.loadClass( "Test2");
//使用Class.forName()來載入類,預設會執行初始化塊
// Class.forName("Test2");
//使用Class.forName()來載入類,並指定ClassLoader,初始化時不執行靜態塊
// Class.forName("Test2", false, loader);
}
} public class Test2 {
static {
System.out.println( "靜態初始化塊執行了。");
}
} 分別切換載入方式,會有不同的輸出結果。 六、自訂ClassLoader 為了說明問題,先看例子: package test;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 自訂ClassLoader
*
* @author leizhimin 2009-7-29 22:05:48
*/
public class MyClassLoader {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException {
URL url = new URL( "file:/E://projects//testScanner//out//production//testScanner");
ClassLoader myloader = new URLClassLoader( new URL[]{url});
Class c = myloader.loadClass( "test.Test3");
System.out.println( "----------");
Test3 t3 = (Test3) c.newInstance();
}
} public class Test3 {
static {
System.out.println( "Test3的靜態初始化塊執行了。");
}
} 運行後: ----------
Test3的靜態初始化塊執行了。
Process finished with exit code 0 可以看出自訂了ClassLoader myloader = new URLClassLoader(new URL[]{url});已經成功將類Test3載入到記憶體了,並通過預設構造方法構造了對象Test3 t3 = (Test3) c.newInstance(); 有關ClassLoader還有很重要一點: 同一個ClassLoader載入的類檔案,只有一個Class執行個體。但是,如果同一個類檔案被不同的ClassLoader載入,則會有兩份不同的ClassLoader執行個體(前提是著兩個類載入器不能用相同的父類載入器)。