引導(Bootstrap)類加載器
引導類加載器主要加載的是JVM自身需要的類,這個類加載使用C++語言實現的,是虛擬機自身的一部分,它負責將 <JAVA_HOME>/lib
路徑下的核心類庫或-Xbootclasspath
參數指定的路徑下的jar包加載到內存中,注意必由於虛擬機是按照文件名識別加載jar包的,如rt.jar,如果文件名不被虛擬機識別,即使把jar包丟到lib目錄下也是沒有作用的(出於安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類)。
擴展(Extension)類加載器
擴展類加載器是指Sun公司(已被Oracle收購)實現的sun.misc.Launcher$ExtClassLoader
類,由Java語言實現的,是Launcher的靜態內部類,它負責加載<JAVA_HOME>/lib/ext
目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫,開發者可以直接使用標准擴展類加載器。
//ExtClassLoader類中獲取路徑的代碼 private static File[] getExtDirs() { //加載<JAVA_HOME>/lib/ext目錄中的類庫 String s = System.getProperty("java.ext.dirs"); File[] dirs; if (s != null) { StringTokenizer st = new StringTokenizer(s, File.pathSeparator); int count = st.countTokens(); dirs = new File[count]; for (int i = 0; i < count; i++) { dirs[i] = new File(st.nextToken()); } } else { dirs = new File[0]; } return dirs; }
系統(System)類加載器
也稱應用程序加載器是指 Sun公司實現的sun.misc.Launcher$AppClassLoader
。它負責加載系統類路徑java -classpath
或-D java.class.path
指定路徑下的類庫,也就是我們經常用到的classpath路徑,開發者可以直接使用系統類加載器,一般情況下該類加載是程序中默認的類加載器,通過ClassLoader#getSystemClassLoader()
方法可以獲取到該類加載器。
在Java的日常應用程序開發中,類的加載幾乎是由上述3種類加載器相互配合執行的,在必要時,我們還可以自定義類加載器,需要注意的是,Java虛擬機對class文件采用的是按需加載的方式,也就是說當需要使用該類時才會將它的class文件加載到內存生成class對象,而且加載某個類的class文件時,Java虛擬機采用的是雙親委派模式即把請求交由父類處理,它一種任務委派模式,下面我們進一步了解它。
總結:
Bootstrap是負責加載系統類的,ExtClassLoader負責加載擴展類的,AppClassLoader負責加載應用類的
2.類加載器三種機制
委托:當加載一個Class時,當前加載器會先委托父加載器加載 如果父加載器沒能加載 則自己加載
可見:父加載器加載的Class,對子加載器都是可見的,而子類加載的Class父加載器不知道
單一:一個Class只能被加載一次
類加載器之間的關系(需要注意的是這里並沒有輸出啟動類加載器,這是由於有些 JDK 的實現對於父類加載器是啟動類加載器的情況,getParent()
方法返回null
。)
-
啟動類加載器,由C++實現,沒有父類。
-
拓展類加載器(ExtClassLoader),由Java語言實現,父類加載器為啟動類加載器
-
系統類加載器(AppClassLoader),由Java語言實現,父類加載器為ExtClassLoader
-
自定義類加載器,父類加載器肯定為AppClassLoader。
-
類加載器的代理模式
類加載器在嘗試自己去查找某個類的字節代碼並定義它時,會先代理給其父類加載器,由父類加載器先去嘗試加載這個類,依次類推。在介紹代理模式背后的動機之前,首先需要說明一下 Java 虛擬機是如何判定兩個 Java 類是相同的。Java 虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認為兩個類是相同的。即便是同樣的字節代碼,被不同的類加載器加載之后所得到的類,也是不同的。比如一個 Java 類
com.example.Sample
,編譯之后生成了字節代碼文件Sample.class
。兩個不同的類加載器ClassLoaderA
和ClassLoaderB
分別讀取了這個Sample.class
文件,並定義出兩個java.lang.Class
類的實例來表示這個類。這兩個實例是不相同的。對於 Java 虛擬機來說,它們是不同的類。試圖對這兩個類的對象進行相互賦值,會拋出運行時異常ClassCastException
。 -
了解了這一點之后,就可以理解代理模式的設計動機了。代理模式是為了保證 Java 核心庫的類型安全。所有 Java 應用都至少需要引用
java.lang.Object
類,也就是說在運行的時候,java.lang.Object
這個類需要被加載到 Java 虛擬機中。如果這個加載過程由 Java 應用自己的類加載器來完成的話,很可能就存在多個版本的java.lang.Object
類,而且這些類之間是不兼容的。通過代理模式,對於 Java 核心庫的類的加載工作由引導類加載器來統一完成,保證了 Java 應用所使用的都是同一個版本的 Java 核心庫的類,是互相兼容的。不同的類加載器為相同名稱的類創建了額外的名稱空間。相同名稱的類可以並存在 Java 虛擬機中,只需要用不同的類加載器來加載它們即可。不同類加載器加載的類之間是不兼容的,這就相當於在 Java 虛擬機內部創建了一個個相互隔離的 Java 類空間。這種技術在許多框架中都被用到
- 雖然在絕大多數情況下,系統默認提供的類加載器實現已經可以滿足需求。但是在某些情況下,您還是需要為應用開發出自己的類加載器。比如您的應用通過網絡來傳輸 Java 類的字節代碼,為了保證安全性,這些字節代碼經過了加密處理。這個時候您就需要自己的類加載器來從某個網絡地址上讀取加密后的字節代碼,接着進行解密和驗證,最后定義出要在 Java 虛擬機中運行的類來。下面將通過兩個具體的實例來說明類加載器的開發。