ClassLoader的工做原理
每一個運行中的線程都有一個成員contextClassLoader,用來在運行時動態地載入其它類
系統默認的contextClassLoader是systemClassLoader,因此通常而言java程序在執行時可使用JVM自帶的類、$JAVA_HOME/jre/lib/ext/中的類和$CLASSPATH/中的類
可使用Thread.currentThread().setContextClassLoader(...);更改當前線程的contextClassLoader,來改變其載入類的行爲
ClassLoader被組織成樹形,通常的工做原理是:
1) 線程須要用到某個類,因而contextClassLoader被請求來載入該類
2) contextClassLoader請求它的父ClassLoader來完成該載入請求
3) 若是父ClassLoader沒法載入類,則contextClassLoader試圖本身來載入
Java中一共有四個類加載器,之因此叫類加載器,是程序要用到某個類的時候,要用類加載器載入內存。
這四個類加載器分別爲:Bootstrap ClassLoader、Extension ClassLoader、AppClassLoader
和URLClassLoader,他們的做用其實從名字就能夠大概推測出來了。其中AppClassLoader在不少地方被叫作System ClassLoader
Bootstrap ClassLoader是在JVM開始運行的時候加載java的核心類,是用C++編寫的,它用來加載核心類庫,在JVM源代碼中這樣寫道:
static const char classpathFormat[] =
"%/lib/rt.jar:"
"%/lib/i18n.jar:"
"%/lib/sunrsasign.jar:"
"%/lib/jsse.jar:"
"%/lib/jce.jar:"
"%/lib/charsets.jar:"
"%/classes";
Extension ClassLoader是用來加載擴展類,即/lib/ext中的類。
AppClassLoader用來加載Classpath的類,是和咱們關係最密切的類。
URLClassLoader用來加載網絡上遠程的類
1. 預先加載與依需求加載
Java 運行環境爲了優化系統,提升程序的執行速度,在 JRE 運行的開始會將 Java 運行所須要的基本類採用預先加載( pre-loading )的方法所有加載要內存當中,由於這些單元在 Java 程序運行的過程中常常要使用的,主要包括 JRE 的 rt.jar 文件裏面全部的 .class 文件。
當 java.exe 虛擬機開始運行之後,它會找到安裝在機器上的 JRE 環境,而後把控制權交給 JRE , JRE 的類加載器會將 lib 目錄下的 rt.jar 基礎類別文件庫加載進內存,這些文件是 Java 程序執行所必須的,因此係統在開始就將這些文件加載,避免之後的屢次 IO 操做,從而提升程序執行效率。
相對於預先加載,咱們在程序中須要使用本身定義的類的時候就要使用依需求加載方法( load-on-demand ),就是在 Java 程序須要用到的時候再加載,以減小內存的消耗,由於 Java 語言的設計初衷就是面向嵌入式領域的。
2. 隱式加載和顯示加載
Java 的加載方式分爲隱式加載( implicit )和顯示加載( explicit ),上面的例子中就是用的隱式加載的方式。所謂隱式加載就是咱們在程序中用 new 關鍵字來定義一個實例變量, JRE 在執行到 new 關鍵字的時候就會把對應的實例類加載進入內存。隱式加載的方法很常見,用的也不少, JRE 系統在後臺自動的幫助用戶加載,減小了用戶的工做量,也增長了系統的安全性和程序的可讀性。
java
Java代碼 安全
Class c = Class.forName("TestClass"); 網絡
TestClass object = (TestClass)c.newInstance(); jvm
object.method(); 函數
經過 Class 類的 forName (String s) 方法把自定義類 TestClass 加載進來,並經過 newInstance ()方法把實例初始化。事實上 Class 類還不少的功能,這裏就不細講了,有興趣的能夠參考 JDK 文檔。
Class 的 forName() 方法還有另一種形式: Class forName(String s, boolean flag, ClassLoader classloader) , s 表示須要加載類的名稱, flag 表示在調用該函數加載類的時候是否初始化靜態區, classloader 表示加載該類所需的加載器。
forName (String s) 是默認經過 ClassLoader.getCallerClassLoader() 調用類加載器的,可是該方法是私有方法,咱們沒法調用,若是咱們想使用 Class forName(String s, boolean flag, ClassLoader classloader) 來加載類的話,就必需要指定類加載器,能夠經過以下的方式來實現:
Test test = new Test();//Test 類爲自定義的一個測試類;
ClassLoader cl = test. getClass().getClassLoader();
// 獲取 test 的類裝載器;
Class c = Class.forName("TestClass", true, cl);
由於一個類要加載就必須要有加載器,這裏咱們是經過獲取加載 Test 類的加載器 cl 看成加載 TestClass 的類加載器來實現加載的。
3. 自定義類加載機制
以前咱們都是調用系統的類加載器來實現加載的,其實咱們是能夠本身定義類加載器的。利用 Java 提供的 java.net.URLClassLoader 類就能夠實現。下面咱們看一段範例:
try{
URL url = new URL("file:/d:/test/lib/");
URLClassLoader urlCL = new URLClassLoader(new URL[]{url});
Class c = urlCL.loadClass("TestClassA");
TestClassA object = (TestClassA)c.newInstance();
object.method();
}catch(Exception e){
e.printStackTrace();
}
咱們經過自定義的類加載器實現了 TestClassA 類的加載並調用 method ()方法。分析一下這個程序:首先定義 URL 指定類加載器從何處加載類, URL 能夠指向網際網絡上的任何位置,也能夠指向咱們計算機裏的文件系統 ( 包含 JAR 文件 ) 。上述範例當中咱們從 file:/d:/test/lib/ 處尋找類;而後定義 URLClassLoader 來加載所需的類,最後便可使用該實例了。
4. 類加載器的階層體系
討論了這麼多之後,接下來咱們仔細研究一下 Java 的類加載器的工做原理:
當執行 java ***.class 的時候, java.exe 會幫助咱們找到 JRE ,接着找到位於 JRE 內部的 jvm.dll ,這纔是真正的 Java 虛擬機器 , 最後加載動態庫,激活 Java 虛擬機器。虛擬機器激活之後,會先作一些初始化的動做,好比說讀取系統參數等。一旦初始化動做完成以後,就會產生第一個類加載器―― Bootstrap Loader , Bootstrap Loader 是由 C++ 所撰寫而成,這個 Bootstrap Loader 所作的初始工做中,除了一些基本的初始化動做以外,最重要的就是加載 Launcher.java 之中的 ExtClassLoader ,並設定其 Parent 爲 null ,表明其父加載器爲 BootstrapLoader 。而後 Bootstrap Loader 再要求加載 Launcher.java 之中的 AppClassLoader ,並設定其 Parent 爲以前產生的 ExtClassLoader 實體。這兩個加載器都是以靜態類的形式存在的。這裏要請你們注意的是, Launcher$ExtClassLoader.class 與 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加載,因此 Parent 和由哪一個類加載器加載沒有關係。
這三個加載器就構成咱們的 Java 類加載體系。他們分別從如下的路徑尋找程序所須要的類:
BootstrapLoader : sun.boot.class.path
ExtClassLoader: java.ext.dirs
AppClassLoader: java.class.path
這三個系統參量能夠經過 System.getProperty() 函數獲得具體對應的路徑。測試