ClassLoader

博客地址:liuzhengyangjava

Java中的ClassLoader

先拋出幾個問題git

  • 類初始化 static{} 塊會在何時執行
  • Class在方法區內的結構
  • 不一樣類加載器加載一個類,類初始化塊會被執行幾回,不一樣ClassLoader加載的類存放在哪裏,是否指向同一個Class實例
  • 類A觸發了類B的加載,那麼類B的加載器是什麼
  • 如何實現熱部署,即在運行時改變類的行爲,或類替換
  • Class.forName(name) 作了什麼

回答這些問題仍是要參考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包中,能夠以目錄形式存放。設計

加載、連接、初始化過程

  • 若是要加載一個類的時候發現類沒有被加載過(就是JVM中沒有這個類的二進制表示)就會使用類加載器嘗試加載這個二進制表示。
  • 連接階段設計驗證、準備和解析。
  • 驗證用於驗證字節碼格式是否正確。
  • 準備階段會進行static字段的建立,並將這些字段初始化成默認值。這個階段不會執行任何代碼。static 字段的初始化會在初始化階段執行。
  • 一些JVM實現會在準備階段預先計算一些附加數據結構來使以後的類操做更加有效。一個特別的結構就是方法表或其餘的數據結構,能夠在讓任意的調用在實例上的方法無需搜索父類。類的二進制表示經過符號引用引用其餘類、字段、方法構造器等。對於字段和方法,符號引用包括字段、方法所在類的名字以及字段和方法本身的名稱。在符號引用使用前必需要進行解析,解析階段會對符號引用進行檢查,並一般會替換成直接引用。若是解析失敗,會拋出場景的NoSuchMethodError等錯誤。

Java中常見的ClassLoader

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) 方法會經過參數控制由是否進行初始化和由哪一個類加載器加載。

ClassLoader中的loadResource方法

咱們在寫程序時,常常會在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的加載器去加載。

何時回觸發類的初始化

  • 建立類的實例,經過new 或者Class.newInstance
  • 類的初始化方法被調用 invokestatic
  • 類的static字段被綁定 putstatic
  • 類的static字段被使用,而且不是常量
  • 一個類被初始化的時候,它的父類會被先初始化 咱們看到,用兩個加載器加載一個類,初始化塊被執行了兩次。
相關文章
相關標籤/搜索