1、ClassLoader概念
ClassLoader是用來動態的加載class文件到虛擬機中,並轉換成java.lang.class類的一個實例,每一個這樣的實例用來表示一個java類,咱們能夠根據Class的實例獲得該類的信息,並經過實例的newInstance()方法建立出該類的一個對象,除此以外,ClassLoader還負責加載Java應用所需的資源,如圖像文件和配置文件等。java
ClassLoader類是一個抽象類。若是給定類的二進制名稱,那麼類加載器會試圖查找或生成構成類定義的數據。通常策略是將名稱轉換爲某個文件名,而後從文件系統讀取該名稱的「類文件」。ClassLoader類使用委託模型來搜索類和資源。每一個 ClassLoader實例都有一個相關的父類加載器。須要查找類或資源時,ClassLoader實例會在試圖親自查找類或資源以前,將搜索類或資源的任務委託給其父類加載器。 數組
注意:程序在啓動的時候,並不會一次性加載程序所要用的全部class文件,而是根據程序的須要,經過Java的類加載機制來動態加載某個class文件到內存中。服務器
2、JVM平臺提供三層classLoader
- Bootstrap classLoader:採用native code實現,是JVM的一部分,主要加載JVM自身工做須要的類,如java.lang.*、java.uti.*等; 這些類位於$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不繼承自ClassLoader,由於它不是一個普通的Java類,底層由C++編寫,已嵌入到了JVM內核當中,當JVM啓動後,Bootstrap ClassLoader也隨着啓動,負責加載完核心類庫後,並構造Extension ClassLoader和App ClassLoader類加載器。
- ExtClassLoader:擴展的class loader,加載位於$JAVA_HOME/jre/lib/ext目錄下的擴展jar。
- AppClassLoader:系統class loader,父類是ExtClassLoader,加載$CLASSPATH下的目錄和jar;它負責加載應用程序主函數類。
其體系結構圖以下:網絡
若是要實現本身的類加載器,不論是實現抽象列ClassLoader,仍是繼承URLClassLoader類,它的父加載器都是AppClassLoader,由於無論調用哪一個父類加載器,建立的對象都必須最終調用getSystemClassLoader()做爲父加載器,getSystemClassLoader()方法獲取到的正是AppClassLoader。數據結構
注意:Bootstrap classLoader並不屬於JVM的等級層次,它不遵照ClassLoader的加載規則,Bootstrap classLoader並無子類。函數
3、JVM加載class文件到內存有兩種方式
- 隱式加載:不經過在代碼裏調用ClassLoader來加載須要的類,而是經過JVM來自動加載須要的類到內存,例如:當類中繼承或者引用某個類時,JVM在解析當前這個類不在內存中時,就會自動將這些類加載到內存中。
- 顯示加載:在代碼中經過ClassLoader類來加載一個類,例如調用this.getClass.getClassLoader().loadClass()或者Class.forName()。
4、ClassLoader加載類的過程
- 找到.class文件並把這個文件加載到內存中
- 字節碼驗證,Class類數據結構分析,內存分配和符號表的連接
- 類中靜態屬性和初始化賦值以及靜態代碼塊的執行
5、自定義類加載器
一、爲什麼要自定義類加載器?
JVM提供的類加載器,只能加載指定目錄的jar和class,若是咱們想加載其餘位置的類或jar時,例如加載網絡上的一個class文件,默認的ClassLoader就不能知足咱們的需求了,因此須要定義本身的類加載器。post
二、如何實現自定義的類加載器?
咱們實現一個ClassLoader,並指定這個ClassLoader的加載路徑。有兩種方式:this
方式一:繼承ClassLoader,重寫父類的findClass()方法,代碼以下:spa
1 import java.io.ByteArrayOutputStream; 2 import java.io.File; 3 import java.io.FileInputStream; 4 import java.io.IOException; 5 public class PathClassLoader extends ClassLoader 6 { 7 public static final String drive = "d:/"; 8 public static final String fileType = ".class"; 9 10 public static void main(String[] args) throws Exception 11 { 12 PathClassLoader loader = new PathClassLoader(); 13 Class<?> objClass = loader.loadClass("HelloWorld", true); 14 Object obj = objClass.newInstance(); 15 System.out.println(objClass.getName()); 16 System.out.println(objClass.getClassLoader()); 17 System.out.println(obj.getClass().toString()); 18 } 19 20 public Class<?> findClass(String name) 21 { 22 byte[] data = loadClassData(name); 23 return defineClass(name, data, 0, data.length);// 將一個 byte 數組轉換爲 Class// 類的實例 24 } 25 public byte[] loadClassData(String name) 26 { 27 FileInputStream fis = null; 28 byte[] data = null; 29 try 30 { 31 fis = new FileInputStream(new File(drive + name + fileType)); 32 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 33 int ch = 0; 34 while ((ch = fis.read()) != -1) 35 { 36 baos.write(ch); 37 } 38 data = baos.toByteArray(); 39 } catch (IOException e) 40 { 41 e.printStackTrace(); 42 } 43 return data; 44 } 45 }
在第13行,咱們調用了父類的loadClass()方法,該方法使用指定的二進制名稱來加載類,下面是loadClass方法的源代碼:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 第一步先檢查這個類是否已經被加載 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //parent爲父加載器 if (parent != null) { //將搜索類或資源的任務委託給其父類加載器 c = parent.loadClass(name, false); } else { //檢查該class是否被BootstrapClassLoader加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { //若是上述兩步均沒有找到加載的class,則調用findClass()方法 long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
這個方法首先檢查指定class是否已經被加載,若是已被加載過,則調用resolveClass()方法連接指定的類,若是還未加載,則先將搜索類或資源的任務委託給其父類加載器,檢查該class是否被BootstrapClassLoader加載,若是上述兩步均沒有找到加載的class,則調用findClass()方法,在咱們自定義的加載器中,咱們重寫了findClass方法,去咱們指定的路徑下加載class文件。
另外,咱們自定義的類加載器沒有指定父加載器,在JVM規範中不指定父類加載器的狀況下,默認採用系統類加載器即AppClassLoader做爲其父加載器,因此在使用該自定義類加載器時,須要加載的類不能在類路徑中,不然的話根據雙親委派模型的原則,待加載的類會由系統類加載器加載。若是必定想要把自定義加載器須要加載的類放在類路徑中, 就要把自定義類加載器的父加載器設置爲null。
方式二:繼承URLClassLoader類,而後設置自定義路徑的URL來加載URL下的類。
咱們將指定的目錄轉換爲URL路徑,而後重寫findClass方法。
6、實現類的熱部署
一、什麼是類的熱部署?
所謂熱部署,就是在應用正在運行的時候升級軟件,不須要從新啓用應用。
對於Java應用程序來講,熱部署就是運行時更新Java類文件。在基於Java的應用服務器實現熱部署的過程當中,類裝入器扮演着重要的角色。大多數基於Java的應用服務器,包括EJB服務器和Servlet容器,都支持熱部署。
類裝入器不能從新裝入一個已經裝入的類,但只要使用一個新的類裝入器實例,就能夠將類再次裝入一個正在運行的應用程序。
二、如何實現Java類的熱部署
前面的分析,咱們已經知道,JVM在加載類以前會檢查請求的類是否已經被加載過來,也就是要調用findLoadedClass方法查看是否可以返回類實例。若是類已經加載過來,再調用loadClass會致使類衝突。
可是,JVM判斷一個類是不是同一個類有兩個條件:一是看這個類的完整類名是否同樣(包括包名),二是看加載這個類的ClassLoader加載器是不是同一個(既是是同一個ClassLoader類的兩個實例,加載同一個類也會不同)。
因此,要實現類的熱部署能夠建立不一樣的ClassLoader的實例對象,而後經過這個不一樣的實例對象來加載同名的類。