Java ClassLoader (Java類加載器)初探

編者按

???????? 筆者最近在為WebSphere中的項目引入第三方j(luò)ar的過程中遇到一些阻礙。雖然根據(jù)網(wǎng)上的資料解決了問題,但忽然覺得對Java類加載器的認(rèn)識知之甚少,只停留在和反射的配合使用上,遂決定一探究竟。于是有了這篇文章,即備日后查詢又可饗讀者。水平有限,如有紕漏還望大神不吝賜教。


目錄

一、 之乎者也:什么是 Class Loader ?
二、 格物致知:驗證 Class Loader 的加載方式
三、 切磋琢磨:一個簡單的自定義 Class Loader
四、 他山之石:其他框架中的自定義 Class Loader(未完待續(xù))
五、 別有洞天:關(guān)于Class Loader的相關(guān)拓展(未完待續(xù))

一、 之乎者也:什么是 Class Loader ?

1. 簡明定義與說明

  • ????作為Java運(yùn)行時環(huán)境的一部分,Java類加載器動態(tài)地加載Java類到Java虛擬機(jī)中。在Java中,庫通常被打包在Jar文件中。庫中可以包含不同種類的對象, 而在Jar文件中最重要的一種對象類型就是Java類文件(Java class),類文件可以視為一段被命名的代碼。類加載器負(fù)責(zé)定位庫,讀取其中的內(nèi)容并加載庫中的類。加載過程一般是按需進(jìn)行地, 即只有類被調(diào)用時加載過程才會發(fā)生。一個被命名的類只能由指定的類加載器加載一次。每一個類又必須由一個類加載器所加載。

2. Java虛擬機(jī)啟動時使用的Class Loader

序號 類型 查找范圍
1 Bootstrap class loader %JAVA_HOME%/jre/lib
%JAVA_HOME%/jre/classes
-Xbootclasspath1
2 Extensions class loader %JAVA_HOME%/jre/lib/ext
java.ext.dirs
3 System class loader CLASSPATH
-classpath
Mainfest

3. 雙親委托

圖1-1 使用雙親委托機(jī)制的類加載方式.PNG
  1. 當(dāng)前Class Loader首先從自己已經(jīng)加載的類中查詢是否此類已經(jīng)加載,如果已經(jīng)加載則直接返回原來已經(jīng)加載的類。每個類加載器都有自己的加載緩存,當(dāng)一個類被加載了以后就會放入緩存,等下次加載的時候就可以直接返回了。

  2. 當(dāng)前Class Loader的緩存中沒有找到被加載的類的時候,委托父類加載器去加載,父類加載器采用同樣的策略,首先查看自己的緩存,然后委托父類的父類去加載,直到bootstrap Class Loader。

  3. 當(dāng)所有的父類加載器都沒有加載的時候,再由當(dāng)前的類加載器加載,并將其放入它自己的緩存中,以便下次有加載請求的時候直接返回。

  4. 通過自定義Class Loader(具體指重載loadClass方法),Java推薦的雙親委托機(jī)制可以被打破。


二、 格物致知:驗證 Class Loader 的加載方式

驗證上述三個Class Loader的存在并建立對雙親委托機(jī)制的初步認(rèn)識

1. 準(zhǔn)備工作

2.1.1 試驗代碼
//D:/ClassLoaderTest.java
// 如果在IDE中編輯,可能會提示IntMath找不到,無視
import com.google.common.math.IntMath;

public class ClassLoaderTest {
    public static void main(String[] args) {
        logClassLoader( IntMath.class);
    }
    public static <T> void logClassLoader(Class<T> tClass) {
        System.out.println(
            String.format(
                "打印類 %s 的類加載器鏈:", tClass.getCanonicalName()));
        ClassLoader classLoader = tClass.getClassLoader();
        while (classLoader!=null) {
            System.out.println(
                String.format(
                    "類加載器鏈:%s", classLoader.toString()));
            classLoader = classLoader.getParent();
        }
        System.out.println( "類加載器鏈: Bootstrap class loader");
    }
}
2.1.2 第三方Jar包位置
D:\repo\com\google\guava\guava\11.0.2\guava-11.0.2.jar
2.1.3 編譯
javac -classpath ./;D:\repo\com\google\guava\guava\11.0.2\guava-11.0.2.jar 
-encoding utf-8 .\ClassLoaderTest.java

2. 運(yùn)行結(jié)果

????????這里直接使用Java命令的參數(shù)來控制類加載器的搜索路徑, 也可以通過其他方法指定, 詳見 2. 通過其他方式指定類加載器的搜索路徑 一節(jié)

圖2-1 ClassLoader測試一.PNG

圖2-2 ClassLoader測試二.PNG

三、 切磋琢磨:一個簡單的自定義 Class Loader

1. 準(zhǔn)備知識

關(guān)鍵方法 描述
loadClass:(name:String, resolve:boolean)=>Class<?> 用資源名稱加載Class
findClass:(name:String)=>Class<?> 用資源名稱查找Class
defineClass:(name:String, b:byte[], off:int, len:int)=>Class<?> 轉(zhuǎn)換字節(jié)數(shù)組到一個Class的實例
  • 實施步驟
    • 繼承ClassLoader
    • 如果要修改雙親委托的代理方式,重寫loadClass方法
    • 重寫findClass方法

2. 準(zhǔn)備工作

3.2.1 試驗代碼
//D:/Main.java
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        CustomerClassLoader customerClassLoader = new CustomerClassLoader( 
                Main.class.getClassLoader(), 
                "D:/CustomerClassLoaderRepo" );
        Class<?> clazz = customerClassLoader.loadClass( "HelloWorld");
        Object object = clazz.getConstructor(new Class[]{}).newInstance();
        Method method = clazz.getMethod("sayHello", String.class);
        method.invoke( object, "tom");
        logClassLoaders(clazz);
    }
    public static  <T> void logClassLoaders( Class<T> tClass) {
        System.out.println( String.format( 
                "打印類 %s 的類加載器鏈:", tClass.getCanonicalName()));
        ClassLoader classLoader = tClass.getClassLoader();
        while ( classLoader != null) {
            System.out.println( String.format( 
                    "類加載器鏈:%s", classLoader.toString()));
            classLoader = classLoader.getParent();
        }
        System.out.println( "類加載器鏈: Bootstrap class loader");
    }
}

//D:/CustomerClassLoader.java
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class CustomerClassLoader extends ClassLoader {
    private String url_prefix;
    public CustomerClassLoader(ClassLoader parent, String url_prefix) {
        super(parent);
        this.url_prefix = "file:///" + url_prefix;
    }

    @Override
    protected Class<?> findClass(String name) 
            throws ClassNotFoundException {
        byte[] bytes = loadClassData(url_prefix + "/" + name + ".class");
        return super.defineClass(name, bytes, 0, bytes.length);
    }

    private byte[] loadClassData(String name) 
            throws ClassNotFoundException {
        return loadClassDate(Paths.get(URI.create(name)));
    }
    private byte[] loadClassDate(Path path) 
            throws ClassNotFoundException {
        if (    path!=null &&
                path.isAbsolute() &&
                Files.exists( path) &&
                Files.isRegularFile( path)) {
            try {
                return Files.readAllBytes( path);
            } catch ( IOException e) {
                e.printStackTrace();
                throw new ClassNotFoundException("文件讀取錯誤");
            }
        } else {
            throw new ClassNotFoundException("文件地址不符合規(guī)則");
        }
    }
}

//D:/HelloWorld.java
public class HelloWorld {
    public void sayHello( String name) {
        System.out.println( "Hello World, I'm " + name);
    }
}
3.2.2 文件位置
- D:
  - \CustomerClassLoaderRepo
    - HelloWorld.java
    - HelloWorld.class
  - Main.java
  - Main.class
  - CustomerClassLoader.java
  - CustomerClassLoader.class

3. 運(yùn)行結(jié)果

圖3-1 自定義ClassLoader測試.PNG

4. 使用URLClassLoader

3.4.1 試驗代碼

以上只是筆者在學(xué)(發(fā))習(xí)(呆), 實際工作中我們可以使用諸多ClassLoader的子類來提升開發(fā)效率。如下是使用URLClassLoader來實現(xiàn)相同的功能。

圖3-2 URLClassLoader的繼承關(guān)系.PNG
//D:/Main.java
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        //CustomerClassLoader customerClassLoader = new CustomerClassLoader( 
        //        Main.class.getClassLoader(), 
        //        "D:/CustomerClassLoaderRepo" );
        //Class<?> clazz = customerClassLoader.loadClass( "HelloWorld");
        URL url = new URL("file:///D:CustomerClassLoaderRepo/");
        URLClassLoader urlClassLoader = new URLClassLoader(
            new URL[]{url} , Main.class.getClassLoader());
        Class<?> clazz = urlClassLoader.loadClass( "HelloWorld");
        Object object = clazz.getConstructor(new Class[]{}).newInstance();
        Method method = clazz.getMethod("sayHello", String.class);
        method.invoke( object, "tom");
        logClassLoaders(clazz);
        urlClassLoader.close(); //釋放資源
    }
    public static  <T> void logClassLoaders( Class<T> tClass) {
        System.out.println( String.format( 
                "打印類 %s 的類加載器鏈:", tClass.getCanonicalName()));
        ClassLoader classLoader = tClass.getClassLoader();
        while ( classLoader != null) {
            System.out.println( String.format( 
                    "類加載器鏈:%s", classLoader.toString()));
            classLoader = classLoader.getParent();
        }
        System.out.println( "類加載器鏈: Bootstrap class loader");
    }
}
3.4.2 文件位置
  • 同 3.3.2
3.4.3 運(yùn)行結(jié)果
圖3-3 URLClassLoader測試.PNG

四、 他山之石:其他框架中的自定義 Class Loader(未完待續(xù))

1. Spring Boot 中的 Class Loader

2. Tomcat 中的 Class Loader

3. WebSphere 中的 Class Loader


五、 別有洞天:關(guān)于Class Loader的相關(guān)拓展(未完待續(xù))

1. Class Loader與熱部署

2. 自定義加載方式

3. 多線程下的Class Loader


結(jié)語


引用

  1. Java Classloader(Wiki)
  2. Java8 API
  3. the Parent-Delegation Model

1. -Xbootclasspath 命令詳解(未完待續(xù))

2. 通過其他方式指定類加載器的搜索路徑(未完待續(xù))

3. jdk已有的其他ClassLoader實現(xiàn)(未完待續(xù))

4. 與Class Loader有關(guān)的異常(未完待續(xù))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容