1. 程式人生 > >分析JVM雙親委派模型的類載入原始碼 自定義類載入器

分析JVM雙親委派模型的類載入原始碼 自定義類載入器

雙親委派模型下,在父類載入器無法載入的情況下再由當前類載入器去載入。具體的實現邏輯在java.util.ClassLoader抽象類的loadClass方法中。在該方法中,先檢查是否已經載入過,如果沒有,就讓父類載入器載入。如果父類載入器服務載入,就呼叫findClass方法載入。findClass方法是空的,需要使用者過載它。所以,按照這樣的邏輯,我們在使用loadClass之前需要過載findClass方法。

	Boostrap ClassLoader
		|
	Extension ClassLoader
		|
	Application ClassLoader
	|		|
OtherClassLoader1   OtherClassLoader2 ...
  • loadClass
public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}
  • loadClass(String name, boolean resolve):
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1. 檢查是否已經載入過
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                	//當前載入器存在父類載入器,遞迴呼叫父類載入器
                    c = parent.loadClass(name, false);
                } else {
                	//檢查該類是否被Bootstrap載入器載入過,有則返回,否則返回null
                    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.
                long t1 = System.nanoTime();
                //既然父類載入器無法載入,則就是用使用者自己所過載的類載入器
                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) {
        	//連結link所載入的類。該方法名起得不好,有誤導性
            resolveClass(c);
        }
        return c;
    }
}
  • find Class
    findClass的預設實現如下:
protected Class<?> findClass(String name) throws ClassNotFoundException {
	//如果沒有過載,一呼叫就丟擲異常
        throw new ClassNotFoundException(name); 
}

findClass的返回值是Class型別,而我們可以使用defineClass方法將一個byte[]轉換成Class型別。而byte[]資料可以通過檔案讀取class位元組碼檔案獲取。

  • defineClass
    defineClass定義和預設實現如下:
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError  {
        return defineClass(name, b, off, len, null);
}

簡單示例

首先,我們定義一個待載入的普通Java類:Student.java, 其中package為空,可以在任何路徑下編譯該檔案成class位元組碼檔案。

public class Student {
    public void say(){
        System.out.println("hello world");
    }
}

進入window的cmd命令列視窗,進入Student.java所在目錄,使用命令javac Student.java獲得Student.class檔案。
在這裡插入圖片描述

  • 載入Student.class檔案
    接下來就是自定義我們的類載入器。閱讀下面的程式碼,細心的讀者會發現,findClass方法是過載的方法,不過載的話無法執行。下面這段程式碼可以在電腦任何位置執行javac Test.javajava Test來進行測試。
mport java.lang.reflect.Method;
import java.io.FileInputStream;

class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte();
            //將byte[]資料轉換成Class型別的物件
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

	//從硬碟中讀取class位元組碼檔案,並轉換成byte[]資料
    private byte[] loadByte() throws Exception {
        String name = "C:/Users/USER/Desktop/temp/Student.class";
        FileInputStream fis = new FileInputStream(name);
        int len = fis.available();
        byte[] data = new byte[len];
        fis.read(data);
        fis.close();
        return data;

    }
};

public class Test {

    public static void main(String args[]) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        //載入Student的Class型別物件
        Class clazz = classLoader.loadClass("Student");
        Object obj = clazz.newInstance();
        //使用反射的方式獲取和執行say()方法
        Method helloMethod = clazz.getDeclaredMethod("say", null);
        helloMethod.invoke(obj, null);
    }
}

謝謝!