JVM的ClassLoader過程分析

本文來自網絡:深刻分析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代碼塊。

相關文章
相關標籤/搜索