深入研究Java類加載機制(轉載)

類加載是Java程序運行的第一步,研究類的加載有助於瞭解JVM執行過程,並指導開發者採取更有效的措施配合程序執行。
研究類加載機制的第二個目的是讓程序能動態的控制類加載,比如熱部署等,提高程序的靈活性和適應性。
 
一、簡單過程
 
Java程序運行的場所是內存,當在命令行下執行:
java HelloWorld
命令的時候,JVM會將HelloWorld.class加載到內存中,並形成一個Class的對象HelloWorld.class。
其中的過程就是類加載過程:
、尋找jre目錄,尋找jvm.dll,並初始化JVM;
、產生一個Bootstrap Loader(啓動類加載器);
、Bootstrap Loader自動加載Extended Loader(標準擴展類加載器),並將其父Loader設爲Bootstrap Loader。
、Bootstrap Loader自動加載AppClass Loader(系統類加載器),並將其父Loader設爲Extended Loader。
、最後由AppClass Loader加載HelloWorld類。
 
以上就是類加載的最一般的過程。
 
二、類加載器各自搜索的目錄
 
爲了弄清楚這個問題,首先還要看看System類的API doc文檔。
 
 

相關值的描述
java.version    Java 運行時環境版本
java.vendor    Java 運行時環境供應商
java.vendor.url    Java 供應商的 URL
java.home    Java 安裝目錄
java.vm.specification.version    Java 虛擬機規範版本
java.vm.specification.vendor    Java 虛擬機規範供應商
java.vm.specification.name    Java 虛擬機規範名稱
java.vm.version    Java 虛擬機實現版本
java.vm.vendor    Java 虛擬機實現供應商
java.vm.name    Java 虛擬機實現名稱
java.specification.version    Java 運行時環境規範版本
java.specification.vendor    Java 運行時環境規範供應商
java.specification.name    Java 運行時環境規範名稱
java.class.version    Java 類格式版本號
java.class.path    Java 類路徑
java.library.path    加載庫時搜索的路徑列表
java.io.tmpdir    默認的臨時文件路徑
java.compiler    要使用的 JIT 編譯器的名稱
java.ext.dirs    一個或多個擴展目錄的路徑
os.name    操作系統的名稱
os.arch    操作系統的架構
os.version    操作系統的版本
file.separator    文件分隔符(在 UNIX 系統中是“/”)
path.separator    路徑分隔符(在 UNIX 系統中是“:”)
line.separator    行分隔符(在 UNIX 系統中是“/n”)
user.name    用戶的賬戶名稱
user.home    用戶的主目錄
user.dir    用戶的當前工作目錄
 
可惜這個幫助文檔並不全,直接用程序打印出來如下:
                for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
                        System.out.println(entry.getKey()+"\t"+entry.getValue());
                }
 
java.runtime.name    Java(TM) SE Runtime Environment
sun.boot.library.path    Q:\jdk6\jre\bin
java.vm.version    14.0-b16
java.vm.vendor    Sun Microsystems Inc.
java.vendor.url    http://java.sun.com/
path.separator    ;
idea.launcher.port    7532
java.vm.name    Java HotSpot(TM) Client VM
file.encoding.pkg    sun.io
sun.java.launcher    SUN_STANDARD
user.country    CN
sun.os.patch.level    Service Pack 3
java.vm.specification.name    Java Virtual Machine Specification
user.dir    E:\projects\testScanner
java.runtime.version    1.6.0_14-b08
java.awt.graphicsenv    sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs    Q:\jdk6\jre\lib\endorsed
os.arch    x86
java.io.tmpdir    C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\
line.separator     
java.vm.specification.vendor    Sun Microsystems Inc.
user.variant     
os.name    Windows XP
sun.jnu.encoding    GBK
java.library.path    Q:\jdk6\bin;.;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;Q:\jdk6\bin;Q:\JavaFX\javafx-sdk1.2\bin;Q:\JavaFX\javafx-sdk1.2\emulator\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\MySQL Server 5.1\bin;C:\Program Files\StormII\Codec;C:\Program Files\StormII
java.specification.name    Java Platform API Specification
java.class.version    50
sun.management.compiler    HotSpot Client Compiler
os.version    5.1
user.home    d:\我的文檔
user.timezone     
java.awt.printerjob    sun.awt.windows.WPrinterJob
idea.launcher.bin.path    C:\IDEA8\bin
file.encoding    UTF-8
java.specification.version    1.6
java.class.path    Q:\jdk6\jre\lib\alt-rt.jar;Q:\jdk6\jre\lib\charsets.jar;Q:\jdk6\jre\lib\deploy.jar;Q:\jdk6\jre\lib\javaws.jar;Q:\jdk6\jre\lib\jce.jar;Q:\jdk6\jre\lib\jsse.jar;Q:\jdk6\jre\lib\management-agent.jar;Q:\jdk6\jre\lib\plugin.jar;Q:\jdk6\jre\lib\resources.jar;Q:\jdk6\jre\lib\rt.jar;Q:\jdk6\jre\lib\ext\dnsns.jar;Q:\jdk6\jre\lib\ext\localedata.jar;Q:\jdk6\jre\lib\ext\sunjce_provider.jar;Q:\jdk6\jre\lib\ext\sunmscapi.jar;Q:\jdk6\jre\lib\ext\sunpkcs11.jar;E:\projects\testScanner\out\production\testScanner;C:\IDEA8\lib\idea_rt.jar
user.name    Administrator
java.vm.specification.version    1
java.home    Q:\jdk6\jre
sun.arch.data.model    32
user.language    zh
java.specification.vendor    Sun Microsystems Inc.
awt.toolkit    sun.awt.windows.WToolkit
java.vm.info    mixed mode, sharing
java.version    1.6.0_14
java.ext.dirs    Q:\jdk6\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
sun.boot.class.path    Q:\jdk6\jre\lib\resources.jar;Q:\jdk6\jre\lib\rt.jar;Q:\jdk6\jre\lib\sunrsasign.jar;Q:\jdk6\jre\lib\jsse.jar;Q:\jdk6\jre\lib\jce.jar;Q:\jdk6\jre\lib\charsets.jar;Q:\jdk6\jre\classes
java.vendor    Sun Microsystems Inc.
file.separator    \
java.vendor.url.bug    http://java.sun.com/cgi-bin/bugreport.cgi
sun.io.unicode.encoding    UnicodeLittle
sun.cpu.endian    little
sun.desktop    windows
sun.cpu.isalist     
、Bootstrap Loader(啓動類加載器):加載System.getProperty("sun.boot.class.path")所指定的路徑或jar。
、Extended Loader(標準擴展類加載器ExtClassLoader):加載System.getProperty("java.ext.dirs")所指定的路徑或jar。在使用Java運行程序時,也可以指定其搜索路徑,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld
、AppClass Loader(系統類加載器AppClassLoader):加載System.getProperty("java.class.path")所指定的路徑或jar。在使用Java運行程序時,也可以加上-cp來覆蓋原有的Classpath設置,例如: java -cp ./lavasoft/classes HelloWorld
 
ExtClassLoader和AppClassLoader在JVM啓動後,會在JVM中保存一份,並且在程序運行中無法改變其搜索路徑。如果想在運行時從其他搜索路徑加載類,就要產生新的類加載器。
 
三、類加載器的特點
、運行一個程序時,總是由AppClass Loader(系統類加載器)開始加載指定的類。
、在加載類時,每個類加載器會將加載任務上交給其父,如果其父找不到,再由自己去加載。
、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。
 
五、類的加載
 
類加載有三種方式:
、命令行啓動應用時候由JVM初始化加載
、通過Class.forName()方法動態加載
、通過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實例(前提是着兩個類加載器不能用相同的父類加載器)。
 
-----------------------
推薦幾篇優秀的文章:
http://nonopo.javaeye.com/blog/208007
http://nonopo.javaeye.com/blog/208012

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章