本文來自網絡:深刻分析Java ClassLoader原理java
http://my.oschina.net/zhengjian/blog/133836c++
1、 JVM的ClassLoader過程以及裝載原理算法
ClassLoader就是尋找類或是接口的字節碼文件(.class)並經過解析字節碼文件來構造類或接口對象的過程。在Java中,類裝載器把一個類裝入Java虛擬機中,要通過三個步驟來完成:尋找文件、連接和初始化,其中連接又能夠分紅校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工做以下:bootstrap
導入class字節碼:查找class文件,導入class或者接口的字節碼;api
連接:執行下面的校驗、準備和解析步驟,其中解析步驟是能夠選擇的;數組
校驗:檢查導入類或接口的二進制數據的正確性;安全
準備:給類的靜態變量分配並初始化存儲空間;網絡
解析:將符號引用轉成直接引用;eclipse
初始化:激活類的靜態變量的初始化Java代碼和靜態Java代碼塊。jvm
2、classpath的做用:查找class文件
classpath的做用是指定查找類的路徑:當使用java命令執行一個類(類中的main方法)時,會從classpath中進行查找這個類。
設置classpath方式一:
設置環境變量CLASSPATH,多個路徑之間使用英文的分號隔開,也能夠指定爲jar包路徑。
示例:CLASSPATH=c:/myclasses/;c/mylib/aa.jar;c:/mylib/bb.jar;.
注意:在Windows中不區分大小寫,因此指定的環境變量名爲classpath或是ClassPath都同樣。
設置classpath方式二:
執行java命令時經過-classpath參數指定。
示例:java -classpath c:/myclasses/;c:/mylib/aa.jar cn.itcast.MainApp
注意:這樣就只用這個參數指定的classpath,找不到類就報錯,不會使用CLASSPATH環境變量!
結論:按classpath中指定的順序,先從前面的路徑中查找,若是找不到,在從下一個路徑中查找,直到找到類字節碼或是報NoClassDefFoundError。
另一種指定class路徑方式:將class類字節碼文件打成jar包,並放到JRE的lib/ext/目錄下,這樣在執行時就能夠直接找到這個類而不須要指定classpath。
如何將class類打包爲jar文件?java的安裝目錄下面bin目錄下有一個工具:jar.exe、eclipse 自帶的export 和Ant, Maven之類的構建均可以輕鬆打包。
命令:jar cvf jar包的名字.jar 文件 文件 文件
例如:jar cvf a.jar HelloWorld.class Test.class
怎樣打開jar包呢?
使用通常的解壓工具就能夠了。
問題如何使用java的工具執行MyEclipseKeyGen.jar呢?
好比MyEclipse6.0的註冊機就是一個jar文件。
操做以下:java -jar MyEclipseKeyGen.jar。
3、Java環境中ClassLoader
java應用環境中不一樣的class分別由不一樣的ClassLoader負責加載。一個jvm中默認的classloader有Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader:
1) Bootstrap ClassLoader稱啓動類加載器,是Java類加載層次中最頂層的類加載器,負責加載JDK中的核心類庫,主要是 %JRE_HOME/lib/ 目錄下的rt.jar、resources.jar、charsets.jar,可經過以下程序得到該類加載器從哪些地方加載了相關的jar或class文件。Bootstrap ClassLoader不繼承自ClassLoader,由於它不是一個普通的Java類,底層由C++編寫,已嵌入到了JVM內核當中,當JVM啓動後,Bootstrap ClassLoader也隨着啓動,負責加載完核心類庫後,並構造Extension ClassLoader和App ClassLoader類加載器。能夠經過sun.boot.class.path系統屬性查詢本機JDK環境:
System.out.println(System.getProperty("sun.boot.class.path"));
2) Extension ClassLoader 稱爲擴展類加載器,負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目下的全部jar。
3) App ClassLoader系統類加載器,負責加載當前java應用的classpath中的全部類。
4) 自定義ClassLoader(),自定義的ClassLoader都必須繼承自抽象類java.lang.ClassLoader,重寫findClass方法,這個方法定義了ClassLoader查找class的方式。
主要能夠擴展的方法有:
findClass 定義查找Class的方式
defineClass 將類文件字節碼加載爲jvm中的class
findResource 定義查找資源的方式
能夠直接使用或繼承已有的ClassLoader實現,好比java.net.URLClassLoader、java.security.SecureClassLoader、 java.rmi.server.RMIClassLoader。
Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子類。
這個是URLClassLoader的構造方法:
public URLClassLoader(URL[] urls, ClassLoader parent)
public URLClassLoader(URL[] urls)urls參數是須要加載的ClassPath url數組,能夠指定parent ClassLoader,不指定的話默認以當前調用類的ClassLoader爲parent。
代碼以下:
ClassLoader classLoader = new URLClassLoader(urls);
Thread.currentThread().setContextClassLoader(classLoader);
Class clazz=classLoader.loadClass("com.company.MyClass");//使用loadClass方法加載class,這個class是在urls參數指定的classpath下邊。
Method taskMethod = clazz.getMethod("doTask", String.class, String.class);//而後咱們就能夠用反射作些事情了
taskMethod.invoke(clazz.newInstance(),"hello","world");
4、ClassLoader加載類的原理
一、 原理介紹
ClassLoader使用的是雙親委託模型來搜索類的,每一個ClassLoader實例都有一個父類加載器的引用(不是繼承的關係,是一個包含的關係),虛擬機內置的類加載器(Bootstrap ClassLoader)自己沒有父類加載器,但能夠用做其它ClassLoader實例的的父類加載器。當一個ClassLoader實例須要加載某個類時,它會試圖親自搜索某個類以前,先把這個任務委託給它的父類加載器,這個過程是由上至下依次檢查的,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,若是沒加載到,則把任務轉交給Extension ClassLoader試圖加載,若是也沒加載到,則轉交給App ClassLoader 進行加載,若是它也沒有加載獲得的話,則返回給委託的發起者,由它到指定的文件系統或網絡等URL中加載該類。若是它們都沒有加載到這個類時,則拋出ClassNotFoundException異常。不然將這個找到的類生成一個類的定義,並將它加載到內存當中,最後返回這個類在內存中的Class實例對象。
二、 爲何要使用雙親委託這種模型呢?
由於這樣能夠避免重複加載。當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。考慮到安全因素,咱們試想一下,若是不使用這種委託模式,那咱們就能夠隨時使用自定義的String來動態替代java核心api中定義的類型,這樣會存在很是大的安全隱患,而雙親委託的方式,就能夠避免這種狀況,由於String已經在啓動時就被引導類加載器(Bootstrcp ClassLoader)加載,因此用戶自定義的ClassLoader永遠也沒法加載一個本身寫的String,除非你改變JDK中ClassLoader搜索類的默認算法。
三、 JVM在搜索類的時候,又是如何斷定兩個class是相同的呢?
JVM在斷定兩個class是否相同時,不只要判斷兩個類名是否相同,並且要判斷是否由同一個類加載器實例加載的。只有二者同時知足的狀況下,JVM才認爲這兩個class是相同的。就算兩個class是同一份class字節碼,若是被兩個不一樣的ClassLoader實例所加載,JVM也會認爲它們是兩個不一樣class。好比網絡上的一個Java類org.classloader.simple.NetClassLoaderSimple,javac編譯以後生成字節碼文件NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB這兩個類加載器並讀取了NetClassLoaderSimple.class文件,並分別定義出了java.lang.Class實例來表示這個類,對於JVM來講,它們是兩個不一樣的實例對象,但它們確實是同一份字節碼文件,若是試圖將這個Class實例生成具體的對象進行轉換時,就會拋運行時異常java.lang.ClassCaseException,提示這是兩個不一樣的類型。
5、ClassLoader加載類的流程
Bootstrap ClassLoader是JVM級別的,由C++撰寫;Extension ClassLoader、App ClassLoader都是java類,都繼承自URLClassLoader超類。Bootstrap ClassLoader由JVM啓動,而後初始化sun.misc.Launcher ,sun.misc.Launcher初始化Extension ClassLoader、App ClassLoader。
在JAVA中,一個類一般有着一個.class文件,但也有例外。在JAVA運行時環境中(Java runtime),每個類都有一個以第一類(first-class)的Java對象所表現出現的代碼,其是java.lang.Class的實例。編譯一個JAVA文件,編譯器都會嵌入一個public, static, final修飾的類型爲java.lang.Class,名稱爲class的域變量在其字節碼文件中。由於使用了public修飾,能夠採用以下的形式對其訪問:
java.lang.Class klass = Myclass.class;
一旦一個類被載入JVM中,同一個類就不會被再次載入了(切記,同一個類)。這裏存在一個問題就是什麼是「同一個類」?正如一個對象有一個具體的狀態,即標識,一個對象始終和其代碼(類)相關聯。同理,載入JVM的類也有一個具體的標識,一個類用其徹底匹配類名(fully qualified class name)做爲標識,這裏指的徹底匹配類名包括包名和類名。但在JVM中一個類用其全名和一個加載類ClassLoader的實例做爲惟一標識。所以,若是一個名爲Pg的包中,有一個名爲Cl的類,被類加載器KlassLoader的一個實例kl1加載,Cl的實例,即C1.class在JVM中表示爲(Cl, Pg, kl1)。這意味着兩個類加載器的實例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不一樣的,被它們所加載的類也所以徹底不一樣,互不兼容的。
例1,測試你所使用的JVM的ClassLoader
import java.net.URL; public class Sample { public static void main(String[] args) { ClassLoader cloader; cloader = ClassLoader.getSystemClassLoader(); System.out.println(cloader); while (cloader != null ) { cloader = cloader.getParent(); System.out.println(cloader); } try { Class<Object> c1 = (Class<Object>)Class.forName("java.lang.Object" ); cloader = c1.getClassLoader(); System.out.println( "java.lang.Object's loader is " + cloader); Class<Sample> c2 = (Class<Sample>) Class.forName("Sample" ); cloader = c2.getClassLoader(); System.out.println( " Sample's loader is " + cloader); } catch (Exception e) { e.printStackTrace(); } } }
在個人機器上( Java 1.7.2)的運行結果
sun.misc.Launcher$AppClassLoader@4d905742
sun.misc.Launcher$ExtClassLoader@3f50d5d6
null
java.lang.Object's loader is null
Sample's loader is sun.misc.Launcher$AppClassLoader@4d905742
第一行表示,系統類裝載器實例化自類sun.misc.Launcher$AppClassLoader
第二行表示,系統類裝載器的parent實例化自類sun.misc.Launcher$ExtClassLoader
第三行表示,系統類裝載器parent的parent爲bootstrap
第四行表示,核心類java.lang.Object是由bootstrap裝載的
第五行表示,用戶類Sample是由系統類裝載器裝載的
注意,咱們清晰的看見這個三個ClassLoader類之間的父子關係(不是繼承關係),父子關係在ClassLoader的實現中有一個ClassLoader類型的屬性,咱們能夠在本身實現自定義的ClassLoader的時候初始化定義,而這三個系統定義的ClassLoader的父子關係分別是
AppClassLoader——————》(Parent)ExtClassLoader——————————》(parent)BootClassLoader(null c++實現)
系統爲何要分別指定這麼多的ClassLoader類呢?
答案在於由於java是動態加載類的,這樣的話,能夠節省內存,用到什麼加載什麼,就是這個道理,然而系統在運行的時候並不知道咱們這個應用與須要加載些什麼類,那麼,就採用這種逐級加載的方式
(1)首先加載核心API,讓系統最基本的運行起來
(2)加載擴展類 (3)加載用戶自定義的類
import java.net.URL; public class Sample { public static void main(String[] args) { System.out.println(System.getProperty("sun.boot.class.path")); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(System.getProperty("java.class.path")); } }
在上面的結果中,你能夠清晰看見三個ClassLoader分別加載類的路徑;也知道爲何咱們在編寫程序的時候,要把用到的jar包放在工程的classpath下面啦,也知道咱們爲何能夠不加載java.lang.*包啦!其中java.lang.*就在rt.jar包中。
六 總結:
classloader有Bootstrap ClassLoader(JVM默認 C++編寫)、Extension ClassLoader和App ClassLoader
也可有自定義classloader。
經過classloader 裝載類的流程:
導入class字節碼:查找class文件,導入class或者接口的字節碼;
連接:執行下面的校驗、準備和解析步驟,其中解析步驟是能夠選擇的;
校驗:檢查導入類或接口的二進制數據的正確性;
準備:給類的靜態變量分配並初始化存儲空間;
解析:將符號引用轉成直接引用;
初始化:激活類的靜態變量的初始化Java代碼和靜態Java代碼塊。