對於虛擬機的角度來看,只存在兩種類加載器: 啓動類加載器(Brootstrap ClassLoader)和「其餘類加載器」。啓動類加載器是由C++寫的,屬於虛擬機的一部分,其餘類加載器都是由java語言實現,獨立於虛擬機外部,所有繼承自抽象類java.lang.ClassLoader。java
從開發的角度來看,有三種類加載器:git
1)啓動類加載器(Bootstrap ClassLoader):這個類加載器主要是負責加載${JAVA_HOME}/lib目錄的jar(好比rt.jar、resources.jar)或者被-Xbootclasspath參數所指定的路徑中的jar。算法
(調用ClassLoader類的loadClass方法加載一個類,並非對類的主動使用,不會致使類的初始化。)編程
2)擴展類加載器(Extension ClassLoader):它負責加載${JAVA_HOME}/lib/ext目錄或者被java.ext.dirs系統變量所指定的路徑中的全部類庫,開發者能夠直接使用擴展類加載器。bootstrap
3)應用類加載器(Application ClassLoader):這個類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,也就是調用ClassLoader.getSystemClassLoader()可獲取該類加載器,因此又叫系統類加載器。它負責JVM啓動時加載來自命令java中的-classpath或者java.class.path系統屬性或者CLASSPATH操做系統屬性所指定的JAR類包和類路徑。加載用戶類路徑(ClassPath)上所指定的類庫。用戶自定義的任何類加載器都將該類加載器作爲它的父類加載器windows
類加載器的層次結構:api
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搜索類的默認算法。安全
比較兩個類是否「相等」,只有這兩個類是由同一個類加載器加載的前提下才有意義,不然,即便這兩個類來自同一個.class文件,被同一個虛擬機加載,只要加載他們的類加載器不一樣,這兩個類就一定不相等。這裏的相等包括Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括instanceof關鍵字作對象所屬關係斷定等狀況。例如,咱們能夠編寫一個類加載器,它能夠拒絕加載沒有標記爲"paid for"的類。微信
若是要編寫本身的類加載器,只須要繼承抽象類 ClassLoader 而後重寫findClass()方法。ClassLoader 中的 loadClass() 方法用於將類的加載操做委託給其父類加載器去進行,只有當該類還沒有加載而且父類加載器也沒法加載該類時,才調用findClass()方法。
全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其餘Class也將由該類加載器負責載入,除非顯示使用另一個類加載器來載入。
父類委託,先讓父類加載器試圖加載該類,只有在父類加載器沒法加載該類時才嘗試從本身的類路徑中加載該類。
緩存機制,緩存機制將會保證全部加載過的Class都會被緩存,當程序中須要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是爲何修改了Class後,必須重啓JVM,程序的修改纔會生效。
咱們能夠編寫本身的用於特殊目的的類加載器,這使得咱們能夠在向虛擬機傳遞字節碼以前執行定製的檢查。
import java.io.*; /** * @ProjectName: base-project * @Description: 實現本身的類加載器 */ public class MyClassLoader extends ClassLoader { private String name; //類加載器的名字 private String path = "D:\\"; //加載類的路徑 private final String fileType = ".class"; //class文件的擴展名 public MyClassLoader(String name) { super(); this.name = name; } public MyClassLoader(ClassLoader parent, String name) { super(parent); this.name = name; } @Override public String toString() { return "MyClassLoader{" + "name='" + name + '\'' + '}'; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } @Override public Class<?> findClass(String name) throws ClassNotFoundException { byte[] data = loadClassData(name); return defineClass(name, data, 0, data.length); } private byte[] loadClassData(String name) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = null; try { this.name = this.name.replace(".", "\\"); //流讀取文件 is = new FileInputStream(new File(path + name + fileType)); baos = new ByteArrayOutputStream(); int ch = 0; while(-1 != (ch = is.read())) { baos.write(ch); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { baos.close(); is.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } public static void test(ClassLoader loader) throws Exception{ Class clazz = loader.loadClass("Sample"); Object object = clazz.newInstance(); } public static void main(String[] args) throws Exception { MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("D:\\myapp\\serverlib\\"); MyClassLoader loader2 = new MyClassLoader(loader1, "loader2"); loader2.setPath("D:\\myapp\\clientlib\\"); MyClassLoader loader3 = new MyClassLoader(null, "loader3"); loader3.setPath("D:\\myapp\\otherlib\\"); test(loader2); // System.out.println("=================="); test(loader3); // Class clazz = loader1.loadClass("Sample"); // Object obj = clazz.newInstance(); //建立一個Sample類的對象 // Sample sample = (Sample)obj; // System.out.println(sample.v1); } }
怎麼運行這個程序來觀察類加載過程呢?
一、在D: 盤下分別創建文件夾
D:\myapp\serverlib\
D:\myapp\clientlib\
D:\myapp\otherlib\
D:\myapp\syslib\
二、新建Dog.java、Sample.java
public class Dog { public Dog() { System.out.println("Dog is loaded by : " + this.getClass().getClassLoader()); } }
public class Sample { public int v1 = 1; public Sample() { System.out.println("Sample is loaded by: " + this.getClass().getClassLoader()); new Dog(); } }
三、把MyClassLoader.class、Dog.class、Sample.class三個.class文件分別放到(注意:要把代碼中的package去掉)
這個時候 clientlib目錄和other目錄沒有任何的.class文件
public static void main(String[] args) throws Exception { MyClassLoader loader1 = new MyClassLoader("loader1"); loader1.setPath("D:\\myapp\\serverlib\\"); MyClassLoader loader2 = new MyClassLoader(loader1, "loader2"); loader2.setPath("D:\\myapp\\clientlib\\"); MyClassLoader loader3 = new MyClassLoader(null, "loader3"); loader3.setPath("D:\\myapp\\otherlib\\"); test(loader2); // System.out.println("=================="); test(loader3);
四、在windows環境下進入DOS命令窗口進入到 D:\myapp\syslib 目錄下
五、運行 java MyClassLoader.class 看輸入
爲何會有這種輸出呢?
先是bootstrap類加載器加載,發現沒有找到,而後是Extentian類加載器加載發現也沒有找到,App類加載器加載也沒有找到,而後loader1類加載器加載發現找到了,就中止加載了。
https://gitee.com/play-happy/base-project
參考:
[1] 《深刻理解Java虛擬機:JVM高級特性與最佳實踐》,周志明 ,機械工業出版社
[2] 《Java 核心技術卷》,機械工業出版社
[3] 博客,http://blog.csdn.net/xyang81/article/details/7292380
[4] 併發編程網,http://ifeve.com/classloader%E8%A7%A3%E6%83%91/