Java虛擬機類加載順序研究

Java虛擬機類加載順序研究


當JVM(Java虛擬機)啓動時,會形成由三個類加載器組成的初始類加載器層次結構:

Bootstrap Classloader
主要負責jdk_home/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。
Extension Classloader
主要負責jdk_home/lib/ext目錄下的jar包或 -Djava.ext.dirs 指定目錄下的jar包裝入工作。
System Classloader

主要負責java -classpath/-Djava.class.path所指的目錄下的類與jar包裝入工作。


1. Bootstrap Classloader -引導(也稱爲原始)類加載器,這個加載器的是非常特殊的,它實際上不是 java.lang.ClassLoader的子類,而是由JVM自身實現的
它所加載的有如下的資源:
D:/Program Files/Java/jre6/lib/resources.jar
D:/Program Files/Java/jre6/lib/rt.jar
D:/Program Files/Java/jre6/lib/sunrsasign.jar
D:/Program Files/Java/jre6/lib/jsse.jar
D:/Program Files/Java/jre6/lib/jce.jar
D:/Program Files/Java/jre6/lib/charsets.jar
D:/Program Files/Java/jre6/classes
不需要在系統屬性CLASSPATH中指定這些類庫,因爲JVM在啓動的時候就自動加載它們了。

2.Extension Classloader -擴展類加載器 ExtClassLoader,它負責加載JRE的擴展目錄(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系統屬性指定的)中JAR的類包,它已經屬於最上層的加載器了。它所加載的有如下的資源:
D:\Program Files\Java\jre6\lib\ext;C:\WINDOWS\Sun\Java\lib\ext

3. System Classloader -系統(也稱爲應用)類加載器AppClassLoader 加載來自在命令java中的-classpath或者java.class.path系統屬性或者 CLASSPATH操作系統屬性所指定的JAR類包和類路徑。也就是加載項目中用到的第三方包和項目中的類,它的父加載器爲ExtClassLoader
它所加載的有如下的資源:
E:\works\encoder\lib\antlr-2.7.6.jar
E:\works\encoder\lib\cglib-2.2.jar
E:\works\encoder\lib\cglib-nodep-2.1_3.jar
E:\works\encoder\lib\commons-codec-1.1.jar
E:\works\encoder\lib\commons-collections-3.2.jar
E:\works\encoder\lib\commons-lang-2.1.jar
E:\works\encoder\lib\commons-logging-1.0.4.jar
E:\works\encoder\lib\commons-logging.jar
E:\works\encoder\lib\dom4j-1.4.jar
E:\works\encoder\lib\ehcache-0.9.jar
E:\works\encoder\lib\gameserver_core_1.0.1.jar
E:\works\encoder\lib\hibernate-annotations.jar
E:\works\encoder\lib\hibernate-commons-annotations.jar
E:\works\encoder\lib\hibernate-entitymanager.jar
E:\works\encoder\lib\hibernate-tools.jar
E:\works\encoder\lib\hibernate2.jar
E:\works\encoder\lib\hibernate3.jar
E:\works\encoder\lib\jakarta-oro.jar
E:\works\encoder\lib\jasypt-1.5.jar
E:\works\encoder\lib\javassist-3.9.0.GA.jar
E:\works\encoder\lib\jta.jar
E:\works\encoder\lib\jzlib-1.0.7.jar
E:\works\encoder\lib\libthrift.jar
E:\works\encoder\lib\log4j-1.2.15.jar
E:\works\encoder\lib\mina-integration-beans-2.0.0-M4.jar
E:\works\encoder\lib\mysql-connector-java-5.0.8-bin.jar
E:\works\encoder\lib\persistence.jar
E:\works\encoder\lib\proxool-0.9.1.jar
E:\works\encoder\lib\proxool-cglib.jar
E:\works\encoder\lib\slf4j-api-1.5.6.jar
E:\works\encoder\lib\slf4j-log4j12-1.5.6.jar
E:\works\encoder\lib\spring.jar
E:\works\encoder\lib\springside3-core-3.1.4.jar
E:\works\encoder\lib\xerces-2.6.2.jar

E:\works\encoder\lib\xml-apis.jar


classloader 加載類用的是全盤負責委託機制。所謂全盤負責,即是當一個classloader加載一個Class的時候,這個Class所依賴的和引用的所有 Class也由這個classloader負責載入,除非是顯式的使用另外一個classloader載入;委託機制則是先讓parent(父)類加載器 (而不是super,它與parent classloader類不是繼承關係)尋找,只有在parent找不到的時候才從自己的類路徑中去尋找。此外類加載還採用了cache機制,也就是如果 cache中保存了這個Class就直接返回它,如果沒有才從文件中讀取和轉換成Class,並存入cache,這就是爲什麼我們修改了Class但是必須重新啓動JVM才能生效的原因。


類加載器的順序是:
先是bootstrap classloader,然後是extension classloader,最後纔是system classloader。大家會發現加載的Class越是重要的越在靠前面。這樣做的原因是出於安全性的考慮,試想如果system classloader“親自”加載了一個具有破壞性的“java.lang.System”類的後果吧。這種委託機制保證了用戶即使具有一個這樣的類,也把它加入到了類路徑中,但是它永遠不會被載入,因爲這個類總是由bootstrap classloader來加載的。
System.out.println("bootstrap classloader -引導(也稱爲原始)類加載器");
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
System.out.println("extension classloader -擴展類加載器");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println("system classloader -系統(也稱爲應用)類加載器");
System.out.println(System.getProperty("java.class.path").replace(";", "\n"));

========================================


JAVA中的每一個類都是通過類加載器加載到內存中的。對於類加載器的工作流程如下表示:
1.searchfile()

找到所要加載的類文件。

2.loadDataClass()
讀取這個類文件的字節碼。
3.defineClass()

加載類文件。


關於Package權限。Java語言規定,在同一個包中的class,如果沒有修飾符,默認爲Package權限,包內的class都可以訪問。但是這還不夠準確。確切的說,只有由同一個ClassLoader裝載的class才具有以上的Package權限。比如啓動類裝載器裝載了java.lang.String,類路徑裝載器裝載了我們自己寫的java.lang.Test,它們不能互相訪問對方具有Package權限的方法。這樣就阻止了惡意代碼訪問核心類的Package權限方法。
在 Java 虛擬機判斷兩個類是否相同的時候,類加載器使用的是類的定義類加載器。

瞭解ClassLoader
1, 什麼是 ClassLoader?
Java 程序並不是一個可執行文件,是需要的時候,才把裝載到 JVM中。ClassLoader 做的工作就是 JVM 中將類裝入內存。 而且,Java ClassLoader 就是用 Java 語言編寫的。這意味着您可以創建自己的 ClassLoader
ClassLoader 的基本目標是對類的請求提供服務。當 JVM 需要使用類時,它根據名稱向 ClassLoader 請求這個類,然後 ClassLoader 試圖返回一個表示這個類的 Class 對象。 通過覆蓋對應於這個過程不同階段的方法,可以創建定製的 ClassLoader。
2, 一些重要的方法
A) 方法 loadClass
ClassLoader.loadClass() 是 ClassLoader 的入口點。該方法的定義如下:
Class loadClass( String name, boolean resolve ;
name JVM 需要的類的名稱,如 Foo 或 java.lang.Object。
resolve 參數告訴方法是否需要解析類。在準備執行類之前,應考慮類解析。並不總是需要解析。如果 JVM 只需要知道該類是否存在或找出該類的超類,那麼就不需要解析。
B) 方法 defineClass
defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節組成的數組並把它轉換成 Class 對象。原始數組包含如從文件系統或網絡裝入的數據。defineClass 管理 JVM 的許多複雜、神祕和倚賴於實現的方面 -- 它把字節碼分析成運行時數據結構、校驗有效性等等。不必擔心,您無需親自編寫它。事實上,即使您想要這麼做也不能覆蓋它,因爲它已被標記成final的。
C) 方法 findSystemClass
findSystemClass 方法從本地文件系統裝入文件。它在本地文件系統中尋找類文件,如果存在,就使用 defineClass 將原始字節轉換成 Class 對象,以將該文件轉換成類。當運行 Java 應用程序時,這是 JVM 正常裝入類的缺省機制。(Java 2 中 ClassLoader 的變動提供了關於 Java 版本 1.2 這個過程變動的詳細信息。) 對於定製的 ClassLoader,只有在嘗試其它方法裝入類之後,再使用 findSystemClass。原因很簡單:ClassLoader 是負責執行裝入類的特殊步驟,不是負責所有類。例如,即使 ClassLoader 從遠程的 Web 站點裝入了某些類,仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類不是我們所關心的,所以要 JVM 以缺省方式裝入它們:從本地文件系統。這就是 findSystemClass 的用途。
D) 方法 resolveClass
正如前面所提到的,可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 loadClass 時,可以調用 resolveClass,這取決於 loadClass 的 resolve 參數的值。

E) 方法 findLoadedClass
findLoadedClass 充當一個緩存:當請求 loadClass 裝入類時,它調用該方法來查看 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應首先調用該方法。
3, 怎麼組裝這些方法
1) 調用 findLoadedClass 來查看是否存在已裝入的類。
2) 如果沒有,那麼採用那種特殊的神奇方式來獲取原始字節。
3) 如果已有原始字節,調用 defineClass 將它們轉換成 Class 對象。
4) 如果沒有原始字節,然後調用 findSystemClass 查看是否從本地文件系統獲取類。
5) 如果 resolve 參數是 true,那麼調用 resolveClass 解析 Class 對象。
6) 如果還沒有類,返回 ClassNotFoundException。
4,Java 2 中 ClassLoader 的變動
1)loadClass 的缺省實現
定製編寫的 loadClass 方法一般嘗試幾種方式來裝入所請求的類,如果您編寫許多類,會發現一次次地在相同的、很複雜的方法上編寫變量。 在 Java 1.2 中 loadClass 的實現嵌入了大多數查找類的一般方法,並使您通過覆蓋 findClass 方法來定製它,在適當的時候 findClass 會調用 loadClass。 這種方式的好處是您可能不一定要覆蓋 loadClass;只要覆蓋 findClass 就行了,這減少了工作量。
2)新方法:findClass
loadClass 的缺省實現調用這個新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代碼,而無需要複製其它代碼(例如,當專門的方法失敗時,調用系統 ClassLoader)。
3) 新方法:getSystemClassLoader
如果覆蓋 findClass 或 loadClass,getSystemClassLoader 使您能以實際 ClassLoader 對象來訪問系統 ClassLoader(而不是固定的從 findSystemClass 調用它)。
4) 新方法:getParent
爲了將類請求委託給父代 ClassLoader,這個新方法允許 ClassLoader 獲取它的父代 ClassLoader。當使用特殊方法,定製的 ClassLoader 不能找到類時,可以使用這種方法。
父代 ClassLoader 被定義成創建該 ClassLoader 所包含代碼的對象的 ClassLoader。
發佈了26 篇原創文章 · 獲贊 11 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章