免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開(kāi)通VIP
Java虛擬機(jī)類加載器及雙親委派機(jī)制
所謂的類加載器(Class Loader)就是加載Java類到Java虛擬機(jī)中的,前面《面試官,不要再問(wèn)我“Java虛擬機(jī)類加載機(jī)制”了》中已經(jīng)介紹了具體加載class文件的機(jī)制。本篇文章我們重點(diǎn)介紹加載器和雙親委派機(jī)制。

類加載器

JVM中有三類ClassLoader構(gòu)成:?jiǎn)?dòng)類(或根類)加載器(Bootstrap ClassLoader)、擴(kuò)展類加載器(ExtClassLoader)、應(yīng)用類加載器(AppClassLoader)。不同的類加載器負(fù)責(zé)不同區(qū)域的類的加載。 啟動(dòng)類加載器:這個(gè)加載器不是一個(gè)Java類,而是由底層的c 實(shí)現(xiàn),負(fù)責(zé)將存放在JAVA_HOME下lib目錄中的類庫(kù),比如rt.jar。因此,啟動(dòng)類加載器不屬于Java類庫(kù),無(wú)法被Java程序直接引用,用戶在編寫自定義類加載器時(shí),如果需要把加載請(qǐng)求委派給引導(dǎo)類加載器,那直接使用null代替即可。

擴(kuò)展類加載器:由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),負(fù)責(zé)加載JAVA_HOME下lib\ext目錄下的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù),開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器。

應(yīng)用類加載器:由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)的。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader方法的返回值,所以也叫系統(tǒng)類加載器。它負(fù)責(zé)加載用戶類路徑上所指定的類庫(kù),可以被直接使用。如果未自定義類加載器,默認(rèn)為該類加載器。

可以通過(guò)這種方式打印加載路徑及相關(guān)jar:

System.out.println("boot:"   System.getProperty("sun.boot.class.path"));System.out.println("ext:"   System.getProperty("java.ext.dirs"));System.out.println("app:"   System.getProperty("java.class.path"));

在打印的日志中,可以看到詳細(xì)的路徑以及路徑下面都包含了哪些類庫(kù)。由于打印內(nèi)容較多,這里就不展示了。

類加載器的初始化

除啟動(dòng)類加載器外,擴(kuò)展類加載器和應(yīng)用類加載器都是通過(guò)類sun.misc.Launcher進(jìn)行初始化,而Launcher類則由根類加載器進(jìn)行加載。相關(guān)代碼如下:

public Launcher() {    Launcher.ExtClassLoader var1;    try {        //初始化擴(kuò)展類加載器,構(gòu)造函數(shù)沒(méi)有入?yún)?,無(wú)法獲取啟動(dòng)類加載器        var1 = Launcher.ExtClassLoader.getExtClassLoader();    } catch (IOException var10) {        throw new InternalError("Could not create extension class loader", var10);    }    try {        //初始化應(yīng)用類加載器,入?yún)閿U(kuò)展類加載器        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);    } catch (IOException var9) {        throw new InternalError("Could not create application class loader", var9);    }    // 設(shè)置上下文類加載器    Thread.currentThread().setContextClassLoader(this.loader);       //...}

雙親委派模型

雙親委派模型:當(dāng)一個(gè)類加載器接收到類加載請(qǐng)求時(shí),會(huì)先請(qǐng)求其父類加載器加載,依次遞歸,當(dāng)父類加載器無(wú)法找到該類時(shí)(根據(jù)類的全限定名稱),子類加載器才會(huì)嘗試去加載。

雙親委派中的父子關(guān)系一般不會(huì)以繼承的方式來(lái)實(shí)現(xiàn),而都是使用組合的關(guān)系來(lái)復(fù)用父加載器的代碼。

通過(guò)編寫測(cè)試代碼,進(jìn)行debug,可以發(fā)現(xiàn)雙親委派過(guò)程中不同類加載器之間的組合關(guān)系。

而這一過(guò)程借用一張時(shí)序圖來(lái)查看會(huì)更加清晰。

ClassLoader#loadClass源碼

ClassLoader類是一個(gè)抽象類,但卻沒(méi)有包含任何抽象方法。繼承ClassLoader類并重寫findClass方法便可實(shí)現(xiàn)自定義類加載器。但如果破壞上面所述的雙親委派模型來(lái)實(shí)現(xiàn)自定義類加載器,則需要繼承ClassLoader類并重寫loadClass方法和findClass方法。

ClassLoader類的部分源碼如下:

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{    //進(jìn)行類加載操作時(shí)首先要加鎖,避免并發(fā)加載    synchronized (getClassLoadingLock(name)) {        //首先判斷指定類是否已經(jīng)被加載過(guò)        Class<?> c = findLoadedClass(name);        if (c == null) {            long t0 = System.nanoTime();            try {                if (parent != null) {                    //如果當(dāng)前類沒(méi)有被加載且父類加載器不為null,則請(qǐng)求父類加載器進(jìn)行加載操作                    c = parent.loadClass(name, false);                } else {                   //如果當(dāng)前類沒(méi)有被加載且父類加載器為null,則請(qǐng)求根類加載器進(jìn)行加載操作                    c = findBootstrapClassOrNull(name);                }            } catch (ClassNotFoundException e) {            }            if (c == null) {                long t1 = System.nanoTime();               //如果父類加載器加載失敗,則由當(dāng)前類加載器進(jìn)行加載,                c = findClass(name);                //進(jìn)行一些統(tǒng)計(jì)操作               // ...            }        }        //初始化該類        if (resolve) {            resolveClass(c);        }        return c;    }}

上面代碼中也提現(xiàn)了不同類加載器之間的層級(jí)及組合關(guān)系。

為什么使用雙親委派模型

雙親委派模型是為了保證Java核心庫(kù)的類型安全。所有Java應(yīng)用都至少需要引用java.lang.Object類,在運(yùn)行時(shí)這個(gè)類需要被加載到Java虛擬機(jī)中。如果該加載過(guò)程由自定義類加載器來(lái)完成,可能就會(huì)存在多個(gè)版本的java.lang.Object類,而且這些類之間是不兼容的。

通過(guò)雙親委派模型,對(duì)于Java核心庫(kù)的類的加載工作由啟動(dòng)類加載器來(lái)統(tǒng)一完成,保證了Java應(yīng)用所使用的都是同一個(gè)版本的Java核心庫(kù)的類,是互相兼容的。

上下文類加載器

子類加載器都保留了父類加載器的引用。但如果父類加載器加載的類需要訪問(wèn)子類加載器加載的類該如何處理?最經(jīng)典的場(chǎng)景就是JDBC的加載。

JDBC是Java制定的一套訪問(wèn)數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)接口,它包含在Java基礎(chǔ)類庫(kù)中,由根類加載器加載。而各個(gè)數(shù)據(jù)庫(kù)廠商的實(shí)現(xiàn)類庫(kù)是作為第三方依賴引入使用的,這部分實(shí)現(xiàn)類庫(kù)是由應(yīng)用類加載器進(jìn)行加載的。

獲取Mysql連接的代碼:

//加載驅(qū)動(dòng)程序Class.forName("com.mysql.jdbc.Driver");//連接數(shù)據(jù)庫(kù)Connection conn = DriverManager.getConnection(url, user, password);

DriverManager由啟動(dòng)類加載器加載,它使用到的數(shù)據(jù)庫(kù)驅(qū)動(dòng)(com.mysql.jdbc.Driver)是由應(yīng)用類加載器加載的,這就是典型的由父類加載器加載的類需要訪問(wèn)由子類加載器加載的類。

這一過(guò)程的實(shí)現(xiàn),看DriverManager類的源碼:

//建立數(shù)據(jù)庫(kù)連接底層方法private static Connection getConnection(        String url, java.util.Properties info, Class<?> caller) throws SQLException {    //獲取調(diào)用者的類加載器    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;    synchronized(DriverManager.class) {        //由啟動(dòng)類加載器加載的類,該值為null,使用上下文類加載器        if (callerCL == null) {            callerCL = Thread.currentThread().getContextClassLoader();        }    }    //...    for(DriverInfo aDriver : registeredDrivers) {        //使用上下文類加載器去加載驅(qū)動(dòng)        if(isDriverAllowed(aDriver.driver, callerCL)) {            try {                //加載成功,則進(jìn)行連接                Connection con = aDriver.driver.connect(url, info);                //...            } catch (SQLException ex) {                if (reason == null) {                    reason = ex;                }            }        }         //...    }}

在上面的代碼中留意改行代碼:

callerCL = Thread.currentThread().getContextClassLoader();

這行代碼從當(dāng)前線程中獲取ContextClassLoader,而ContextClassLoader在哪里設(shè)置呢?就是在上面的Launcher源碼中設(shè)置的:

// 設(shè)置上下文類加載器Thread.currentThread().setContextClassLoader(this.loader);

這樣一來(lái),所謂的上下文類加載器本質(zhì)上就是應(yīng)用類加載器。因此,上下文類加載器只是為了解決類的逆向訪問(wèn)提出來(lái)的一個(gè)概念,并不是一個(gè)全新的類加載器,本質(zhì)上是應(yīng)用類加載器。

自定義類加載器

自定義類加載器只需要繼承java.lang.ClassLoader類,然后重寫findClass(String name)方法即可,在方法中指明如何獲取類的字節(jié)碼流。

如果要破壞雙親委派規(guī)范的話,還需重寫loadClass方法(雙親委派的具體邏輯實(shí)現(xiàn))。但不建議這么做。

public class ClassLoaderTest extends ClassLoader {    private String classPath;    public ClassLoaderTest(String classPath) {        this.classPath = classPath;    }    /**     * 編寫findClass方法的邏輯     *     * @param name     * @return     * @throws ClassNotFoundException     */    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        // 獲取類的class文件字節(jié)數(shù)組        byte[] classData = getClassData(name);        if (classData == null) {            throw new ClassNotFoundException();        } else {            // 生成class對(duì)象            return defineClass(name, classData, 0, classData.length);        }    }    /**     * 編寫獲取class文件并轉(zhuǎn)換為字節(jié)碼流的邏輯     *     * @param className     * @return     */    private byte[] getClassData(String className) {        // 讀取類文件的字節(jié)        String path = classNameToPath(className);        try {            InputStream is = new FileInputStream(path);            ByteArrayOutputStream stream = new ByteArrayOutputStream();            byte[] buffer = new byte[2048];            int num = 0;            // 讀取類文件的字節(jié)碼            while ((num = is.read(buffer)) != -1) {                stream.write(buffer, 0, num);            }            return stream.toByteArray();        } catch (IOException e) {            e.printStackTrace();        }        return null;    }    /**     * 類文件的完全路徑     *     * @param className     * @return     */    private String classNameToPath(String className) {        return classPath   File.separatorChar                  className.replace('.', File.separatorChar)   ".class";    }    public static void main(String[] args) {        String classPath = "/Users/zzs/my/article/projects/java-stream/src/main/java/";        ClassLoaderTest loader = new ClassLoaderTest(classPath);        try {            //加載指定的class文件            Class<?> object1 = loader.loadClass("com.secbro2.classload.SubClass");            System.out.println(object1.newInstance().toString());        } catch (Exception e) {            e.printStackTrace();        }    }}

打印結(jié)果:

SuperClass static initSubClass static initcom.secbro2.classload.SubClass@5451c3a8

關(guān)于SuperClass和SubClass在上篇文章《面試官,不要再問(wèn)我“Java虛擬機(jī)類加載機(jī)制”了》已經(jīng)貼過(guò)代碼,這里就不再貼出了。

通過(guò)上面的代碼可以看出,主要重寫了findClass獲取class的路徑便實(shí)現(xiàn)了自定義的類加載器。

那么,什么場(chǎng)景會(huì)用到自定義類加載器呢?當(dāng)JDK提供的類加載器實(shí)現(xiàn)無(wú)法滿足我們的需求時(shí),才需要自己實(shí)現(xiàn)類加載器。比如,OSGi、代碼熱部署等領(lǐng)域。

Java9類加載器修改

以上類加載器模型為Java8以前版本,在Java9中類加載器已經(jīng)發(fā)生了變化。在這里主要簡(jiǎn)單介紹一下相關(guān)模型的變化,具體變化細(xì)節(jié)就不再這里展開(kāi)了。

java9中目錄的改變。

Java9中類加載器的改變。

在java9中,應(yīng)用程序類加載器可以委托給平臺(tái)類加載器以及啟動(dòng)類加載器;平臺(tái)類加載器可以委托給啟動(dòng)類加載器和應(yīng)用程序類加載器。

在java9中,啟動(dòng)類加載器是由類庫(kù)和代碼在虛擬機(jī)中實(shí)現(xiàn)的。為了向后兼容,在程序中仍然由null表示。例如,Object.class.getClassLoader()仍然返回null。但是,并不是所有的JavaSE平臺(tái)和JDK模塊都由啟動(dòng)類加載器加載。

舉幾個(gè)例子,啟動(dòng)類加載器加載的模塊是java.base,java.logging,java.prefs和java.desktop。其他JavaSE平臺(tái)和JDK模塊由平臺(tái)類加載器和應(yīng)用程序類加載器加載。

java9中不再支持用于指定引導(dǎo)類路徑,-Xbootclasspath和-Xbootclasspath/p選項(xiàng)以及系統(tǒng)屬性sun.boot.class.path。-Xbootclasspath/a選項(xiàng)仍然受支持,其值存儲(chǔ)在jdk.boot.class.path.append的系統(tǒng)屬性中。

java9不再支持?jǐn)U展機(jī)制。但是,它將擴(kuò)展類加載器保留在名為平臺(tái)類加載器的新名稱下。ClassLoader類包含一個(gè)名為getPlatformClassLoader()的靜態(tài)方法,該方法返回對(duì)平臺(tái)類加載器的引用。

小結(jié)

本篇文章主要基于java8介紹了Java虛擬機(jī)類加載器及雙親委派機(jī)制,和Java8中的一些變化。其中,java9中更深層次的變化,大家可以進(jìn)一步研究一下。該系列持續(xù)更新中,“程序新視界”。

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
徹底搞懂Class.getResource和ClassLoader.getResource的區(qū)別和底層原理
深入理解JVM類加載器
JVM系列(三):雙親委派機(jī)制筆記
解密Java虛擬機(jī)如何加載一個(gè)Class文件
JVM類加載機(jī)制詳解(二)類加載器與雙親委派模型
全面解析Java類加載器
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服