JVM底層之ClassLoader源碼解析及自定義ClassLoader

1.JVM運行流程

JVM運行流程如下圖所示:

JVM運行流程.png

2.JVM基本結(jié)構(gòu)

JVM基本機構(gòu)包括:類加載器,執(zhí)行引擎,運行時數(shù)據(jù)區(qū),本地接口
Class Files-->ClassLoader-->運行時數(shù)據(jù)區(qū)-->執(zhí)行引擎(本地庫接口)-->本地方法庫
具體參考我之前的文章:jvm內(nèi)存模型概述

3.ClassLoader雙親委派模型

由上面的概念可知,ClassLoader的作用就是將字節(jié)碼文件(Class Files)加載到運行時數(shù)據(jù)區(qū)。

3.1類的裝載過程

加載-->連接(驗證、準備、解析)-->初始化-->使用-->卸載

說明:在此只是簡單的介紹一下類的裝載過程,詳細的過程請自行百度。

加載:取得類的字節(jié)碼文件的二進制字節(jié)流,加載之后會產(chǎn)生一個類對象(Class對象:保存類的定義或者結(jié)構(gòu),放入堆中)
初始化:執(zhí)行類的構(gòu)造器,為類的一些靜態(tài)變量賦予正確的初始值
初始化的代碼事例如下圖所示:

類的裝載-初始化.png

注意:static塊中的變量必須在static塊之前聲明,否則只能寫,不能讀。

構(gòu)造器包含以下內(nèi)容:
a.static變量(不能是final修飾的常量)
b.static塊

構(gòu)造器與構(gòu)造方法的區(qū)別:
a.構(gòu)造器:構(gòu)造Class對象
b.構(gòu)造方法:實例化對象,先要執(zhí)行類的構(gòu)造器,才能實例化對象

3.2 ClassLoader簡介

JDK已有的ClassLoader:
Bootstrap ClassLoader(啟動加載器,必須要首先啟動JVM,通過C++實現(xiàn),存在于JVM的內(nèi)核中,用null表示):主要加載jdk lib下的rt.jar等
Extension ClassLoader (Java實現(xiàn),繼承自ClassLoader):加載%JAVA_HOME%/lib/ext/*.jar
App ClassLoader(Java實現(xiàn),繼承自ClassLoader):加載classpath下的jar
自定義類加載器,也必須繼承自ClassLoader:加載自定義路徑下的jar

3.3 雙親委派模型

類加載器都有一個父類加載器先執(zhí)行,他與父類加載器不是繼承關(guān)系,而是組合關(guān)系(成員變量)。
他加載的任務(wù)必須先委托給他的父類加載器,如果父類沒有加載到這個類,則繼續(xù)由發(fā)起加載的這個類加載器加載(子類需要重寫findClass方法),如果沒有加載到,則報出ClassNotFoudException

注意:
a.使用雙親委派模型的原因:避免類的重復(fù)加載,不會產(chǎn)生不必要的安全隱患
b.findClass的目的就是用來實現(xiàn)自定義ClassLoader的方法(建議重寫findClass,不建議重寫loadClass)

4.ClassLoader源碼解析

ClassLoader主要方法LoadClass,實現(xiàn)了加載類的二進制字節(jié)流的功能

loadClass.png

繼續(xù)往下看,

image.png

紅圈序號說明如下:

  1. 先根據(jù)傳入的全限定名稱加載Class對象
  2. 如果class對象為空的情況,分別對應(yīng)以下兩種情況:
    • 如果他的父類加載器不為空,則由父類加載器執(zhí)行類的裝載工作
    • 如果父類加載器為空,則由Bootstrap ClassLoader執(zhí)行類的裝載工作,此時其實由JVM內(nèi)核執(zhí)行加載動作,代碼如下所示:
image.png
  1. 如果class對象為空的話,則由子類的findClass實現(xiàn)類的裝載工作
image.png
image.png

一般情況下,實現(xiàn)自定義類加載器則需要重寫findClass方法即可。

5.從源碼分析實現(xiàn)自定義類加載器

測試代碼用idea開發(fā),代碼結(jié)構(gòu)如下:

代碼結(jié)構(gòu).png

a. 自定義ClassLoader類如下:

package lyx.demo.classloader;

import java.io.*;

/**
 * Created by landyChris on 2017/10/29.
 */
public class MyClassLoader extends ClassLoader {

    private String path;//加載類的路徑
    private String name;//類加載器的名稱

    //指定父類加載器
    public MyClassLoader(ClassLoader parent, String path, String name) {
        super(parent);//顯示指定父類加載器
        this.path = path;
        this.name = name;
    }

    public MyClassLoader(String path, String name) {
        super();//讓系統(tǒng)類加載器成為該類的父加載器
        this.path = path;
        this.name = name;
    }

    /**
     * 通過自定義ClassLoader加載我們自己定義的類
     * @param name 全路徑名稱,類似:lyx.demo.xxx.Demo
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = readClassFile2ByteArray(name);
        return this.defineClass(name,data,0,data.length);
    }

    /**
     * 獲取.class字節(jié)數(shù)組
     * lyx.demo.xxx.Demo-->d:/lyx/demo/xxx/Demo.class
     * @param name
     * @return
     */
    private byte[] readClassFile2ByteArray(String name) {

        InputStream is = null;

        byte returnData[] = null;

        name = name.replaceAll("\\.", File.separator);

        String filePath = this.path + name + ".class";

        File file = new File(filePath);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            is = new FileInputStream(file);

            int tmp = 0;
            while ((tmp = is.read()) != -1){
                baos.write(tmp);
            }
            returnData = baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //關(guān)閉流
            try {
                if(is != null) {
                    is.close();
                }
                if(baos != null) {
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return returnData;
    }

    @Override
    public String toString() {
        return this.name;
    }
}

b. 新建一個Test類,用于測試用途,代碼如下:

public class Test {
  public Test() {
    System.out.println("A ClassLoader:" + this.getClass().getClassLoader()       + " from classpath");
  }
}

然后在自己的某個盤符下新建一個目錄,比如我電腦是d:/tmp

image.png

還是上面的Test類,只是把打印信息改為以下語句:

System.out.println("A ClassLoader:" + this.getClass().getClassLoader() + " from customer ,path=d:/tmp");
image.png

在d:tmp目錄下編譯好后,如下圖所示:

image.png

c.編寫測試類

package lyx.demo.classloader;

/**
 * Created by landyChris on 2017/10/29.
 */
public class TestClassLoader {
    public static void main(String[] args) throws Exception{
        MyClassLoader loader = new MyClassLoader(null,"d:/tmp/","landy");
        Class<?> c = loader.loadClass("Test");
        c.newInstance();
    }
}

如上代碼,運行結(jié)果如下:

image.png

以上結(jié)果說明:ClassLoader的執(zhí)行是符合雙親委派模型的,它會先讓父類加載器(App ClassLoader)執(zhí)行l(wèi)oadClass的動作,如果找到Test類則直接返回,此例中,在該classpath下是有Test類的,如下圖所示:

image.png

為了達到我們需要執(zhí)行的是d:/tmp目錄下的Test類,我們可以兩種做法,一種是直接刪除idea工程中的Test類。一種是利用:啟動加載器可以用null值表示,我們可以修改以下代碼達到目的:

MyClassLoader loader = new MyClassLoader(null,"d:/tmp/","landy");

結(jié)果如下:

image.png

到此,已得到想要的結(jié)果。

事例代碼地址:https://github.com/landy8530/interview

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

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

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