1. 程式人生 > >JVM 類載入器_3

JVM 類載入器_3

啟動類載入器深入

     內建於JVM中的啟動類載入器會載入java.lang.ClassLoader以及其他的Java平臺類。 當JVM啟動時,一塊特殊的機器碼會執行,它會載入擴充套件類載入器與系統類載入器,這塊特殊的機器碼叫做啟動類載入器(Bootstrap)      啟動類載入器並不是java類,而其他的載入器則都是java類      啟動類載入器是特定於平臺的機器指令,它負責開啟整個載入過程。      所有類載入器(除了啟動類載入器)都被實現為Java類,不過,總歸要有一個元件來載入第一個Java類載入器。從而讓整個載入過程能夠順利進行下去,載入第一個純Java類載入器就是啟動類載入器的職責。      啟動類載入器還會負責載入供JRE正常執行所需要的基本元件,這包括java.util與java.lang包中的類等等。

     System.out.println(ClassLoader.class.getClassLoader());      結果為Null。說明ClassLoader類也是啟動類載入器載入的。

     System.out.println(Launcher.class.getClassLoader());      由於擴充套件類載入器、系統類載入器都是Launcher的靜態內部類,不是public的,並不能訪問到。而我們之前根據例子MySample瞭解到,MyCat是由MySample載入的類載入器進行載入的,同理,系統類載入器、擴充套件類載入器也是由Launcher的類載入器進行載入的,所以用以上語句能夠證實:擴充套件類載入器、系統類載入器都是由啟動類載入器進行載入的

自定義系統類載入器

先自己讀一下Doc文件

Doc文件

譯文

Ps: 自己翻譯的,如果有錯麻煩指出啦~謝謝~

     返還委託機制中的系統類載入器。它是新的ClassLoader例項的預設的委託雙親,它是用來啟動應用的類載入器的典範。      這個方法最早在執行時的啟動序列中被第一次呼叫,在這個時間點它建立了系統類載入器並且將它設定為呼叫Thread類的類載入器的上下文。(這邊總感覺怪怪的)      預設的系統類載入器是一個ClassLoader類的實現依賴的例項。      如果系統屬性java.system.class.loader在getSystemClassLoader方法第一次呼叫的時候已經被定義了(也就是給了初值),那麼這個屬性定義的類將會作為這個方法返還的系統類載入器的類的名字。被定義了這個屬性的類是被預設的系統類載入器載入的

,並且這個類必須定義一個public的帶一個ClassLoader引數的構造方法,用來作為委託雙親。然後用預設的系統類載入器作為引數的這個構造方法將會建立一個例項,這個作為結果的類載入器將會被定義為系統類載入器。

那麼我們來試驗一下

     首先我們自定義一個類載入器,用我們之前的例子:

package com.ssy.jvm.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

/**
 * 建立了一個自己定義的類載入器,該例子用的是系統預設的AppClassLoader做為其雙親
 * 用自己定義的方法來載入類,生成類的例項
 */
public class MyFirstClassLoader extends ClassLoader {
    private final String extSuffix = ".class";
    private String classloaderName;
    private String path;

    /**
     * ClassLoader的doc文件中說明 Each instance of ClassLoader has an associated parent class loader.
     * 翻譯:每個ClassLoader的例項都會有一個與之關聯的父類載入器。
     */
    public MyFirstClassLoader(String classloaderName) {
        // super();可加可不加,會自動呼叫父類不帶引數的構造方法
        // 加上是為了提醒自己這邊用的是系統類載入器(引用類載入器)作為MyFirstClassLoader的雙親;
        super();
        this.classloaderName = classloaderName;
    }

    public MyFirstClassLoader(ClassLoader classLoader) {
        super(classLoader);
    }
    
    public MyFirstClassLoader(String classloaderName, ClassLoader classLoader) {
        // 這邊是用自己定義的類載入器作為MyFirstClassLoader的雙親
        super(classLoader);
        this.classloaderName = classloaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    /**
     * ClassLoader類的loadClass方法裡會呼叫findClass方法來載入這個類。
     *
     * @param className 類的二進位制名字(ClassLoader類的doc中有解釋binary name。類似於java.lang.String)
     * @return 該name所對應的類的Class物件
     */
    @Override
    protected Class<?> findClass(String className) {
        System.out.println("findClass invoked: " + className);
        System.out.println("class loader name: " + this.classloaderName);
        byte[] data = loadClassData(className);
        // 呼叫父類的defineClass方法來返回該類對應的Class物件
        return this.defineClass(className, data, 0, data.length);
    }

    /**
     * 用自己定義的方法來載入類的二進位制檔案
     *
     * @param className 類的二進位制名字(ClassLoader類的doc中有解釋binary className。類似於java.lang.String)
     * @return 載入後的二進位制陣列
     */
    public byte[] loadClassData(String className) {
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        byte[] data = null;
        // mac系統換成 /
        className = className.replace(".", "/");

        try {
            is = new FileInputStream(new File(this.path + className + this.extSuffix));
            baos = new ByteArrayOutputStream();

            int ch;

            while ((ch = is.read()) != -1) {
                baos.write(ch);
            }

            data = baos.toByteArray();

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        MyFirstClassLoader loader1 = new MyFirstClassLoader("loader1");
//        loader1.setPath("/Users/ddcc/IdeaProjects/jvm_lecture/out/production/classes/");
        loader1.setPath("/Users/ddcc/Desktop/");
        Class<?> clazz = loader1.loadClass("com.ssy.jvm.classloader.MyTest1");
        System.out.println("class: " + clazz.hashCode());
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println();

        loader1 = null;
        clazz = null;
        object = null;

        System.gc();
        Thread.sleep(200000);

        loader1 = new MyFirstClassLoader("loader1");
        loader1.setPath("/Users/ddcc/Desktop/");
        clazz = loader1.loadClass("com.ssy.jvm.classloader.MyTest1");
        System.out.println("class: " + clazz.hashCode());
        System.out.println(clazz.newInstance());

        System.out.println();
    }

    /**
     * 測試
     *
     * @param classLoader 類載入器
     */
    public void test(ClassLoader classLoader) throws Exception {
        Class<?> clazz = classLoader.loadClass("com.ssy.jvm.classloader.MyTest1");
        System.out.println(clazz.newInstance());
        System.out.println(clazz.getClassLoader());
    }
}

注意:這個自定義的類載入器需要有一個public的帶ClassLoader引數的構造方法(文件中講的)。

自定義一個測試類

package com.ssy.jvm.classloader;

/**
 * 在執行期,一個Java類是由該類的完全限定名(binary name,二進位制名)
 * 和用於載入該類的定義類載入器(defining loader)所共同決定的。
 * 如果同樣名字(即相同的完全限定名)的類是由兩個不同的載入器所載入,那麼這些類就是不同的。
 * 即便.class檔案的位元組碼完全一樣,並且從相同的位置載入亦是如此。
 */

import sun.misc.Launcher;

public class MyTest23 {
    public static void main(String[] args) {
        // 啟動類載入器載入路徑
        System.out.println(System.getProperty("sun.boot.class.path"));
        // 擴充套件類載入器載入路徑。
        System.out.println(System.getProperty("java.ext.dirs"));
        // 應用類載入器載入路徑。
        System.out.println(System.getProperty("java.class.path"));

        System.out.println(ClassLoader.class.getClassLoader());
        // 擴充套件類載入器與系統類載入器也是由啟動類載入器所載入的。
        // 這邊打出的Launcher類的類載入器可以說明系統類載入器和啟動類載入器的類載入器,
        // 因為擴充套件類載入器和應用類載入器都是Launcher類內部的靜態類,MySample例子可以知道,其內部類也是由載入Launcher的載入器進行載入的
        System.out.println(Launcher.class.getClassLoader());

        System.out.println("-------------------------------");
        // 看一下getSystemClassLoader方法的DOC,
        // 其中說明如果指定java.system.class.loader,那麼會讓系統類載入器指向我們自己定義的載入器
        ClassLoader.getSystemClassLoader();
        // 在預設情況下,這個是沒有被定義的。該屬性是沒有被定義的,系統類載入器預設會指向AppClassLoader
        System.out.println(System.getProperty("java.system.class.loader"));
        System.out.println(MyTest23.class.getClassLoader());
        // 成為了系統類載入器的自定義載入器內部其實還是系統類載入器進行載入的。文件裡也有說。
        System.out.println(MyFirstClassLoader.class.getClassLoader());
        // 現在讓我們自己定義的載入器成為系統類載入器,
        // 我們自己定義的類載入器必須定義一個public的一個ClassLoader引數的構造方法(Doc中說的)給系統呼叫的
        // MyFirstClassLoader中加一個構造方法 super(classLoader)
        // java -Djava.system.class.loader=com.ssy.jvm.classloader.MyFirstClassLoader com.ssy.jvm.classloader.MyTest23
        System.out.println(ClassLoader.getSystemClassLoader());
        // 上面那一行,在控制檯上執行就會變成我們自己定義的類載入器了
    }
}

現在我們在Terminal終端中進行執行: 切入到classes目錄下:

DDCCdeMacBook-Pro:classes ddcc$ pwd
/Users/ddcc/IdeaProjects/jvm_lecture/out/production/classes

執行:

java -Djava.system.class.loader=com.ssy.jvm.classloader.MyFirstClassLoader com.ssy.jvm.classloader.MyTest23

結果:

/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/classes
/Users/ddcc/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
.
null
null
-------------------------------
com.ssy.jvm.classloader.MyFirstClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
com.ssy.jvm.classloader.MyFirstClassLoader@4e25154f

可以發現,最後一行ClassLoader獲得的系統類載入器變成了我們自定義的類載入器了。