類加載器(深入理解Java虛擬機筆記)

概述

在類加載階段中,通過一個類的全限定名來來獲取描述該類的二進制字節流 這個操作放在Java虛擬機中實現,實現這個操作的代碼被稱爲類加載器。

 


類與加載器

對於任意一個類,都必須由加載它的類加載器和和這個類本身 來確定它在虛擬機中的唯一性。每一個類加載器,都擁有一個獨立的類名稱空間,比較兩個類是否相等,只有在兩個類是由同一個類加載器 加載的前提下才有意義,否則,即便這兩個類來源於同一個 Class文件,被同一個Java虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。

此處說的相等 包括equals()方法、isAssignableFrom()方法、isInstance() 方法的返回結果,也包括了使用instanceof關鍵字做對象所屬關係判定等各種情況。

雙親委派模型

從Java開發人員的角度來看,Java保持着三類加載器,雙親委派的類加載架構。

(1)啓動類加載器:這個類加載器負責加載存放在 \lib目錄,或者被-Xbootclasspath參數所指定的路徑中存放的 ,而且是Java虛擬機能夠識別的(按照文件名識別,如rt.jar、tools.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機的內存中。

啓動類加載器無法被Java程序直接引用,我們在編寫自定義類加載器時,如需要把加載請求委派給啓動類加載器,則直接使用null代替。下面是java.lang.ClassLoader.getClassLoader()方法

public ClassLoader getClassLoader() {
        ClassLoader cl = getClassLoader0();
        if (cl == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader ccl = ClassLoader.getCallerClassLoader();
            if (ccl != null && ccl != cl && !cl.isAncestor(ccl)) {
                sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
            }
        }
        return cl;
 }

 

(2)擴展類加載器:該類加載器是在類sun.misc.Launcher$ExtClassLoader 中以Java代碼的形式實現的,它負責加載載\lib\ext目錄中,或者被java.ext.dirs系統變量所 指定的路徑中所有的類庫。 開發者可以直接在程序中使用擴展類加載器來加載Class文件。

(3)應用程序類加載器:這個類加載器由 sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑上所有的類庫,開發者同樣可以直接在代碼中使用這個類加載器。如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

JDK9之前的Java應用都是由上述三類加載器互相配合完成加載的,若用戶認爲有必要,還可加入自定義的類加載器來進行拓展。這些類加載器之間的協作關係如下圖所示,這種層次關係被稱爲類加載器的“雙親委派模型“。

雙親委派模型要求除了頂層的啓動類加載器外,其餘的類加載器都應有自己的父類加載 器。不過這裏類加載器之間的父子關係一般不是以繼承的關係來實現的,而是通常使用組合關係來複用父加載器的代碼。

雙親委派模型的工作過程

如果一個類加載器收到了類加載的請求,首先把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有加載請求最終都應該傳送到最頂層的啓動類加載器中,只有當父加載器反饋自己無法完成這個加載請 求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試自己去完成加載。

使用雙親委派模型來組織類加載器之間關係的好處之一就是:Java中的類隨着它的類加載器一起具備了一種帶優先級的層次關係。例如類java.lang.Object,它存放在rt.jar之中,無論哪一 個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載因此Object類 在程序的各種類加載器環境中都能夠保證是同一個類;若沒有使用此模型,而是都有各個類加載器自行去加載的話,如果用戶自己也編寫了一個名爲java.lang.Object的類,並放在程序的 ClassPath中,那系統中就會出現多個不同的Object類,Java類型體系中最基礎的行爲也就無從保證,應用程序將會變得一片混亂。(自己編寫的可以正常編譯,但永遠無法被加載運行)

雙親委派模型的代碼實現

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
// 首先,檢查請求的類是否已經被加載過了
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
// 如果父類加載器拋出ClassNotFoundException
// 說明父類加載器無法完成加載請求
            }
            if (c == null) {
// 在父類加載器無法加載時
// 再調用本身的findClass方法來進行類加載
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

 


自定義類加載器

自定義的類加載器需要繼承ClassLoader,並覆蓋findClass方法。如下代碼所示,它首先根據類的全名在文件中查找累的字節代碼文件(.class文件),然後讀取文件內容,通過 defineClass() 方法來把這些字節代碼轉換成 java.lang.Class 類的實例。

 

public class People {       //該類寫在記事本里,保存爲.java文件。在用javac命令行編譯成class文件,放在F盤下
    private String name;

    public People() {}

    public People(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return "I am a people, my name is " + name;
    }

}
public class MyClassLoader extends ClassLoader {
    public MyClassLoader() { }
    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    private byte[] getClassBytes(File file) throws Exception {
        //讀入.class字節
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer bbu = ByteBuffer.allocate(1024);

        while (true) {
            int len = fc.read(bbu);
            if(len == 0 || len == -1)
                break;
            bbu.flip();
            wbc.write(bbu);
            bbu.clear();
        }
        fis.close();
        return  baos.toByteArray();
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = new File("F:/People.class");
        try {
            byte[] bArray = getClassBytes(file);
//            defineClass方法可以把二進制流字節組成的文件轉換爲一個java.lang.Class
            Class<?> cl = this.defineClass(name, bArray, 0, bArray.length);
            return cl;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception{
        MyClassLoader mcl = new MyClassLoader();
        Class<?> cl = Class.forName("People", true, mcl);
        Object obj = cl.newInstance();

        System.out.println(obj);
        System.out.println(obj.getClass().getClassLoader());
    }
}

 

java.lang.ClassLoader 的 loadClass() 實現了雙親委派模型的邏輯,自定義類加載器一般不去重寫它

 


上文代碼參選自:

https://blog.csdn.net/SEU_Calvin/article/details/52315125

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章