天天看點

JVM中的類加載器

類加載器

 把類加載階段中的"通過一個類的全限定名來擷取描述此類的二進制位元組流"這個動作放到Java虛拟機外部去實作,以便讓應用程式自己決定如何去擷取所需要的類,實作這個動作的代碼子產品稱為類加載器。

自定義類加載器

 現在有個需求在項目中我們需要加載一個特定目錄下的class檔案【c:\tools\myClassLoader】,這時我們需要自己來定義特定的類加載器。

1.建立自定義類加載器

 繼承ClassLoader後重寫了findClass方法加載指定路徑上的class,代碼如下:

import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * 自定義類加載器
 * @author 波波烤鴨
 * @email [email protected]
 *
 */
public class MyClassLoader extends ClassLoader {
    // 加載的路徑
    private String path;

    public MyClassLoader(String path) {
        super();
        this.path = path;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] result = getClass(name);
            if (result == null) {
                throw new ClassNotFoundException();
            } else {
                // 将位元組流轉換為Class對象
                return defineClass(name, result, 0, result.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    // 加載class為位元組數組
    private byte[] getClass(String name) {
        try {
            return Files.readAllBytes(Paths.get(path));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}      

2.測試

指定目錄下存放編譯好的class檔案,注意用相關的jdk版本編譯

JVM中的類加載器
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    MyClassLoader myLoader = new MyClassLoader("C:\\tools\\myClassLoader\\User.class");
    Class clazz = myLoader.loadClass("com.dpb.pojo.User");
    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}      

輸出結果

com.dpb.pojo.User@4e25154f
com.dpb.loader.MyClassLoader@6d06d69c      

實作了加載特定目錄下的class檔案

ClassLoader

 上面的代碼雖然實作加載特定目錄下的class檔案,但這麼執行的原因是什麼呢?要了解這個我們需要來具體看下ClassLoader的源代碼。代碼比較多,截取了核心的代碼

protected Class<?> loadClass(String name, boolean resolve)
     throws ClassNotFoundException
 {
     synchronized (getClassLoadingLock(name)) {
         // First, check if the class has already been loaded
         Class<?> c = findLoadedClass(name);
         // 擷取類加載器 如果為null 本方法就結束了
         if (c == null) {
             long t0 = System.nanoTime();
             try {
                 // 如果parent為null
                 if (parent != null) {
                    // 擷取 父類加載器
                     c = parent.loadClass(name, false);
                 } else {
                    // 使用引導加載器
                     c = findBootstrapClassOrNull(name);
                 }
             } catch (ClassNotFoundException e) {
                 // ClassNotFoundException thrown if class not found
                 // from the non-null parent class loader
             }
             // 如果所有的父加載器都沒有找到Class
             if (c == null) {
                 // If still not found, then invoke findClass in order
                 // to find the class.
                 long t1 = System.nanoTime();
                 // 就調用自身的findClass方法去加載類
                 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,本方法留給子類去重寫的
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}      

 是以如果我們需要加載特定的Class檔案的時候隻需要重寫findClass方法即可,而不用去重寫loadClass方法。

雙親委派模型

 通過ClassLoader中的loadClass方法我們發現類加載器加類的時候有既定的原則,而且系統提供的類加載器好像也不止一個,我們就來說下這塊。系統給我們提供了三個類加載器,如下

序号 類加載 說明

1 啟動類加載器

Bootstrap ClassLoader 加載<JAVA_HOME> \lib目錄下或-Xbootclasspath指定路徑下能被虛拟機識别的類庫加載到虛拟機中(rj.jar) ,無法被java程式直接是使用

2 擴充類加載器

Extension ClassLoader 負責加載<JAVA_HOME> \lib\ext目錄中或者被java.ext.dirs指定的目錄下的類庫,程式員可以直接使用該加載器

3 應用程式類加載器

Application ClassLoader 也稱系統類加載器,負責加載使用者類路徑上所指定的類庫,一般是程式預設的類加載器

JVM中的類加載器

1.啟動類加載器

public static void main(String[] args) {
    System.out.println("BootStrap:"+String.class.getClassLoader());
    System.out.println(System.getProperty("sun.boot.class.path"));
}      

啟動類加載器我們無法通過程式擷取,是以列印結果為null,可是加載資源的路徑可以擷取。

BootStrap:null
C:\Program Files\Java\jre1.8.0_144\lib\resources.jar;
C:\Program Files\Java\jre1.8.0_144\lib\rt.jar;
C:\Program Files\Java\jre1.8.0_144\lib\sunrsasign.jar;
C:\Program Files\Java\jre1.8.0_144\lib\jsse.jar;
C:\Program Files\Java\jre1.8.0_144\lib\jce.jar;
C:\Program Files\Java\jre1.8.0_144\lib\charsets.jar;
C:\Program Files\Java\jre1.8.0_144\lib\jfr.jar;
C:\Program Files\Java\jre1.8.0_144\classes      

2.擴充類加載器

public static void main(String[] args) {
    System.out.println(System.getProperty("java.ext.dirs"));
}      

加載路徑如下:

C:\Program Files\Java\jre1.8.0_144\lib\ext;
C:\Windows\Sun\Java\lib\ext      

我們也可以将自己的檔案打成jar包放到擴充目錄下,也會被擴充類加載器加載。

3.系統類加載器

public static void main(String[] args) {
    System.out.println(System.getProperty("java.class.path"));
}      

加載路徑

C:\Users\dengp\Desktop\共享檔案\Java1112\workspace\others\FreemarkerDemo\target\classes;
C:\Users\dengp\.m2\repository\org\springframework\spring-context\4.3.21.RELEASE\spring-context-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-aop\4.3.21.RELEASE\spring-aop-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-beans\4.3.21.RELEASE\spring-beans-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-core\4.3.21.RELEASE\spring-core-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-expression\4.3.21.RELEASE\spring-expression-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-webmvc\4.3.21.RELEASE\spring-webmvc-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-web\4.3.21.RELEASE\spring-web-4.3.21.RELEASE.jar;
C:\Users\dengp\.m2\repository\org\freemarker\freemarker\2.3.28\freemarker-2.3.28.jar;
C:\Users\dengp\.m2\repository\org\springframework\spring-context-support\4.3.21.RELEASE\spring-context-support-4.3.21.RELEASE.jar      

雙親委派描述:

   如果一個類加載器收到了類加載請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成,每一個層次的類加載器都是如果,是以所有的加載請求最終都應該傳遞到頂層的啟動類加載器中

   當父加載器回報無法加載該類時(搜尋範圍中沒有找到所需的類),子加載器才會嘗試自己去加載。

弄清楚這個委派模型後再去看loadClass方法中的邏輯應該就比較容易了。

參考《深入了解Java虛拟機》