博客地址:liuzhengyangjava
ClassLoader
先拋出幾個問題git
回答這些問題仍是要參考JDK的代碼實現,另外還要依靠Java語言規範和Java 虛擬機規範github
本篇文章用類來泛指類和接口bootstrap
類加載器是用於加載Class的機制,Class能夠以文件的形式或者是二進制流的形式存在,通常 按照最多見的Class文件來稱呼。數據結構
<!-- -->ide
ClassLoader負責加載類,java.lang.ClassLoader
類是一個抽象類, 能夠經過一個類的二進制名稱,來定位類在哪裏,並生成class
的數據定義。 最多見的策略是將類的名稱轉換成文件名並從文件系統中讀取Class文件
.測試
每一個Class
對象都會有指向定義它的ClassLoader
的引用,經過Class#getClassLoader()
能夠得到。url
類的二進制名(binary name)是Java語言規範規定的,常見的有:spa
java.lang.String test.loader.Test$Test2 // 靜態內部類
Class文件能夠存在jar包中,能夠以目錄形式存放。設計
Java的類加載結構有bootstrap class loader用來加載$JAVA_HOME/jre/lib下載的 rt.jar中的文件,其中是Java的核心類.Bootstrap classloader是JVM中的實現, 若是要在ClassLoader中表示其爲父類,用null表示。 另外有ExtClassLoader加載lib/ext文件夾下的jar包 AppClassLoader是用來加載ClassPath目錄下的jar包和Class文件。 常說這三者是父類關係,並非Java中的集成關係,而是ClassLoader中定義的 parent.
ClassLoader文件在第一次加載類的時候會先委託其父加載器加載,若是加載失敗再本身加載。 這樣,一些關鍵的類,如String等就不會遭到篡改。可是在J2EE中,一個Web容器下 可能有多個應用,每一個應用加載時有子類優先的需求,這時就須要覆蓋默認的邏輯。 代碼邏輯在ClassLoader類的loadClass(String name, boolean resolve)
方法中.ClassLoader#loadClass
調用的是ClassLoader#loadClass(name, false)
resolve表示的是是否進行連接步驟。 去掉一些不相關代碼,loadClass方法的邏輯以下
類加載鎖 synchronized (getClassLoadingLock(name)) { // 查看是否已經被加載過,會調用一個native方法判斷 Class<?> c = findLoadedClass(name); if (c == null) { // 若是沒加載過,則會進入加載過程 try { // 若是parent不是null,則說明是Java中的類加載器 if (parent != null) { // 調用parent的loadClass方法遞歸向上加載 c = parent.loadClass(name, false); } else { // 若是是null,說明是Bootstrap class loader, // 則使用Bootstrap加載器加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 都沒加載成功,則本身進行加載 if (c == null) { // If still not found, then invoke findClass in order // to find the class. // 調用findClass,會找到class的字節流而後使用 // defineClass()來定義類 c = findClass(name); } } if (resolve) { // 若是要連接,則進行連接,其中有驗證、準備和符號引用解析過程 resolveClass(c); } return c; }
另外,Class的靜態方法Class.forName(className)
也可以完成類的加載工做, 返回一個Class對象。會對類進行初始化,使用調用Class.forName的方法所在的類的類加載器 來加載。 Class.forName(className, initialize, loader)
方法會經過參數控制由是否進行初始化和由哪一個類加載器加載。
咱們在寫程序時,常常會在classpath下放置一些配置文件,在運行時讀取配置文件的內容 能夠經過Class.getResource(), 例如
Test.class.getResourceAsStream("/config.properties") 這個方法會委託Test類的加載器來進行加載資源
當咱們判斷兩個Class對象是不是相等時,或判斷是不是集成關係時,須要看它們的類加載器是不是同一個。 類加載器和類,組成了Class對象的標識。 測試代碼
自定義的類加載器,修改默認的委託機制。 private static class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] urls) { super(urls); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return defineClass(name); } } public static class Class1 { static { System.out.println("Initialize class 1"); Class2.doSay(); Class<Class2> class2Class = Class2.class; System.out.println("Class2 Loader is " + class2Class.getClassLoader()); } public static void say() { } } public static class Class2 { static { System.out.println("Initialize Class2"); } public static void doSay() { System.out.println("Say"); } } public static void main(String[] args) throws Exception { URL path = ClassLoaderTest.class.getResource("/"); URL rtPath = Object.class.getResource("/"); MyClassLoader myClassLoader = new MyClassLoader(new URL[]{path, rtPath}); Class<?> class1 = myClassLoader.loadClass("classloader.ClassLoaderTest$Class1"); Class<?> aClass = Class.forName("classloader.ClassLoaderTest$Class1", true, myClassLoader); Class<?> aClass2 = Class.forName("classloader.ClassLoaderTest$Class1"); System.out.println(class1); System.out.println("Class 1 classLoader is " + class1.getClassLoader()); System.out.println(aClass); }
可以看出,類A觸發類B的初始化時,會用類A的加載器去加載。