談談 Java 類載入機制

weixin_33670713發表於2019-01-30

概述

12016719-310c0e81dcf59719.png

類載入器主要分為兩類,一類是 JDK 預設提供的,一類是使用者自定義的。 JDK 預設提供三種類載入器:

Bootstrap ClassLoader 啟動類載入器:每次執行java命令時都會使用該載入器為虛擬機器載入核心類。該載入器是由native code實現,而不是 Java 程式碼,載入類的路徑為<JAVA_HOME>/jre/lib。特別的<JAVA_HOME>/jre/lib/rt.jar中包含了sun.misc.Launcher類, 而sun.misc.Launcher$ExtClassLoader和sun.misc.Launcher$AppClassLoader都是sun.misc.Launcher的內部類,所以擴充類載入器和系統類載入器都是由啟動類載入器載入的。

Extension ClassLoader, 擴充類載入器:用於載入擴充庫中的類。擴充庫路徑為<JAVA_HOME>/jre/lib/ext/。實現類為sun.misc.Launcher$ExtClassLoader

System ClassLoader系統類載入器:用於載入 CLASSPATH 中的類。實現類為sun.misc.Launcher$AppClassLoader

歡迎學Java和大資料的朋友們加入java架構交流: 855835163

群內提供免費的架構資料還有:Java工程化、高效能及分散式、高效能、深入淺出。高架構。效能調優、Spring,MyBatis,Netty原始碼分析和大資料等多個知識點高階進階乾貨的免費直播講解  可以進來一起學習交流哦

使用者自定義的類載入器

Custom ClassLoader, 一般都是java.lang.ClassLoder的子類

正統的類載入機制是基於雙親委派的,也就是當呼叫類載入器載入類時,首先將載入任務委派給雙親,若雙親無法載入成功時,自己才進行類載入。

在例項化一個新的類載入器時,我們可以為其指定一個parent,即雙親,若未顯式指定,則System ClassLoader就作為預設雙親。

具體的說,類載入任務是由ClassLoader的loadClass()方法來執行的,他會按照以下順序載入類:

通過findLoadedClass()看該類是否已經被載入。該方法為 native code 實現,若已載入則返回。

若未載入則委派給雙親,parent.loadClass(),若成功則返回。

若未成功,則呼叫findClass()方法載入類。java.lang.ClassLoader中該方法只是簡單的丟擲一個ClassNotFoundException所以,自定義的 ClassLoader 都需要 OverridefindClass()方法。

類載入API

java.lang.ClassLoader

ClassLoader是一個抽象類。

待載入的類必須用The Java™ Language Specification定義的全類名,全類名的定義請查閱The Form of a Binary

給定一個全類名,類載入器應該去定位該類所在的位置。通用的策略是將全類名轉換為類檔案路徑,然後通過類檔案路徑在檔案系統中定位。

每一個載入到記憶體的類都由一個 Class 物件來表示,每一個 Class 物件都有一個指向載入該類的類載入器的引用。但是陣列的 Class 物件是由 Java 執行時環境建立的,通過Class.getClassLoader()方法返回的是陣列元素的類載入器,若陣列元素是基本型別,則返回null,若類是由Bootstrap ClassLoader載入的話也是返回null。

publicclassMain {

    publicstaticvoidmain(String[] args) {

        // Object 類在 <java_home>/jre/lib/rt.jar 中,

        // 由 Bootstrap ClassLoader 載入,由於該類載入器是由 native code 編寫

        // 所以輸出為 null

        Object[] objects = newObject[5];

        System.out.println();

        System.out.println(objects.getClass().getClassLoader());


        // ZipFileAttributes 類在 <java_home>/jre/lib/ext/zipfs.jar 中,

        // 由 Extension ClassLoader 載入,

        // 輸出為  sun.misc.Launcher$ExtClassLoader@4b67cf4d

        ZipFileAttributes[] attributes = newZipFileAttributes[5];

        System.out.println();

        System.out.println(attributes.getClass().getClassLoader());


        // Main 類是自定義的類,

        // 預設由 System ClassLoader 載入,

        // 輸出為 sun.misc.Launcher$AppClassLoader@18b4aac2

        Main[] array = newMain[5];

        array[0] = newMain();

        System.out.println();

        System.out.println(array.getClass().getClassLoader());

    }

}

ClassLoader預設支援並行載入,但是其子類必須呼叫ClassLoader.registerAsParallelCapable()來啟用並行載入

一般來說,JVM 從本地檔案系統載入類的行為是與平臺有關的。

defineClass()方法可以將位元組流轉換成一個Class物件。然後呼叫Class.newInstance()來建立類的例項

java.security.SecureClassLoader

增加了一層許可權驗證,因為關注點不在安全,所以暫不討論。

java.net.URLClassLoader

該類載入器用來載入 URL 指定的 JAR 檔案或目錄中的類和資源,以/結尾的 URL 認為是目錄,否則認為是 JAR 檔案。

// 嘗試通過 URLClassLoader 來載入桌面下的 Test 類。

publicclassMain {

    publicstaticvoidmain(String[] args) {

        try{

            URL[] urls = newURL[1];

            URLStreamHandler streamHandler = null;

            File classPath = newFile("/home/chen/Desktop/");

            String repository = (newURL("file", null,

                    classPath.getCanonicalPath() + File.separator))

                    .toString();

            urls[0] = newURL(null, repository, streamHandler);


            ClassLoader loader = newURLClassLoader(urls);


            Class testClass = loader.loadClass("Test");


            // output:  java.net.URLClassLoader@7f31245a

            System.out.println(testClass.getClassLoader());

        } catch(MalformedURLException e) {

            e.printStackTrace();

        } catch(IOException e) {

            e.printStackTrace();

        } catch(ClassNotFoundException e) {

            e.printStackTrace();

        }

    }

}

Tomcat 8.5.15類載入機制

12016719-0d461e1905baaa00.png

Tomcat 使用正統的類載入機制(雙親委派),但部分地方做了改動。

Bootstrap classLoader 和 Extension classLoader 的作用不變。

System classLoader正常情況下載入的是CLASSPATH下的類,但是 Tomcat 的啟動指令碼並未使用該變數,而是從以下倉庫下載入類:

$CATALINA_HOME/bin/bootstrap.jar包含了 Tomcat 的啟動類。在該啟動類中建立了Common classLoader、Catalina classLoader、shared classLoader。因為$CATALINA_BASE/conf/catalina.properties中只對common.loader屬性做了定義,server.loader和shared.loader屬性為空,所以預設情況下,這三個 classLoader 都是CommonLoader。具體的程式碼邏輯可以查閱org.apache.catalina.startup.Bootstrap類的initClassLoaders()方法和createClassLoader()方法。

$CATALINA_BASE/bin/tomcat-juli.jar包含了 Tomcat 日誌模組所需要的實現類。

$CATALINA_HOME/bin/commons-daemon.jar。

Common classLoader 是位於 Tomcat 應用伺服器頂層的公用類載入器。由其載入的類可以由 Tomcat 自身類和所有應用程式使用。掃描路徑由 $CATALINA_BASE/conf/catalina.properties 檔案中的 common.loader 屬性定義。預設是 $CATALINA_HOME/lib。

catalina classLoader 用於載入伺服器內部可見類,這些類應用程式不能訪問。

shared classLoader 用於載入應用程式共享類,這些類伺服器不會依賴。

Webapp classLoader 。每個應用程式都會有一個獨一無二的 webapp classloader,他用來載入本應用程式 /WEB-INF/classes 和 /WEB-INF/lib 下的類。

特別的:

Webapp classLoader的預設行為會與正常的雙親委派模式不同:

從Bootstrap classloader載入。

若沒有,從/WEB-INF/classes載入。

若沒有,從/WEB-INF/lib/*.jar載入。

若沒有,則依次從System、Common、shared載入(該步驟使用雙親委派)。

當然了,我們也可以通過配置來使Webapp classLoader嚴格按照雙親委派模式載入類:

通過在工程的META-INF/context.xml(和WEB-INF/classes在同一目錄下) 配置檔案中新增<Loader delegate="true"/>

因為Webapp classLoader的實現類是org.apache.catalina.loader.WebappLoader,他有一個屬性叫delegate, 用來控制類載入器的載入行為,預設為false,我們可以使用set方法,將其設為true來啟用嚴格雙親委派載入模式。

嚴格雙親委派模式載入步驟:

從Bootstrap classloader載入。

若沒有,則依次從System、Common、shared載入。

若沒有,從/WEB-INF/classes載入。

若沒有,從/WEB-INF/lib/*.jar載入。

歡迎學Java和大資料的朋友們加入java架構交流: 855835163

群內提供免費的架構資料還有:Java工程化、高效能及分散式、高效能、深入淺出。高架構。效能調優、Spring,MyBatis,Netty原始碼分析和大資料等多個知識點高階進階乾貨的免費直播講解  可以進來一起學習交流哦

相關文章