成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【修煉內(nèi)功】[JVM] 深入理解JVM之ClassLoader

荊兆峰 / 1904人閱讀

摘要:本文已收錄修煉內(nèi)功躍遷之路在誕生之初便提出,各提供商發(fā)布很多不同平臺(tái)的虛擬機(jī),這些虛擬機(jī)都可以載入并執(zhí)行同平臺(tái)無關(guān)的字節(jié)碼。設(shè)計(jì)者在第一版虛擬機(jī)規(guī)范中便承諾,時(shí)至今日,商業(yè)機(jī)構(gòu)和開源機(jī)構(gòu)已在之外發(fā)展出一大批可以在上運(yùn)行的語言,如等。

本文已收錄【修煉內(nèi)功】躍遷之路

Java在誕生之初便提出 "Write Once, Run Anywhere",各提供商發(fā)布很多不同平臺(tái)的虛擬機(jī),這些虛擬機(jī)都可以載入并執(zhí)行同平臺(tái)無關(guān)的字節(jié)碼。

設(shè)計(jì)者在第一版Java虛擬機(jī)規(guī)范中便承諾 "In the future, we will consider bounded extensions to the Java virtual machine to provide better support for other languages",時(shí)至今日,商業(yè)機(jī)構(gòu)和開源機(jī)構(gòu)已在Java之外發(fā)展出一大批可以在JVM上運(yùn)行的語言,如Clojure、Groovy、JRuby、Jpython、Scala、Kotlin等。這些語言都不是本地可執(zhí)行程序,需要JVM將編譯后的class文件加載后才能夠運(yùn)行,負(fù)責(zé)加載class文件的組件便是ClassLoader.

JVM類加載流程

java語言系統(tǒng)內(nèi)置了眾多類加載器,從一定程度上講,只存在兩種不同的類加載器:一種是啟動(dòng)類加載器,此類加載由C++實(shí)現(xiàn),是JVM的一部分;另一種就是所有其他的類加載器,這些類加載器均由java實(shí)現(xiàn),且全部繼承自java.lang.ClassLoader

Bootstrap ClassLoader 啟動(dòng)類加載器,最頂層的加載類,由C++實(shí)現(xiàn),負(fù)責(zé)加載%JAVA_HOME%/lib目錄中或-Xbootclasspath中參數(shù)指定的路徑中的,并且是虛擬機(jī)識(shí)別的(按名稱)類庫

Extention ClassLoader 擴(kuò)展類加載器,由啟動(dòng)類加載器加載,實(shí)現(xiàn)為sun.misc.Launcher$ExtClassLoader,負(fù)責(zé)加載目錄%JRE_HOME%/lib/ext目錄中或-Djava.ext.dirs中參數(shù)指定的路徑中的jar包和class文件

Application ClassLoader 應(yīng)用類加載器,也稱為系統(tǒng)類加載器(System ClassLoader,可由java.lang.ClassLoader.getSystemClassLoader()獲取),實(shí)現(xiàn)為sun.misc.Launcher$AppClassLoader,由啟動(dòng)類加載器加載,負(fù)責(zé)加載當(dāng)前應(yīng)用classpath下的所有類

雙親委派模型

java語言系統(tǒng)有眾多類加載器,包括用戶自定義類加載器,各加載器之間的加載順序如何?首先從JVM入口應(yīng)用sun.misc.Launcher聊起

Launcher
public Launcher() {
    ExtClassLoader localExtClassLoader;
    try {
        // 加載擴(kuò)展類加載器
        localExtClassLoader = ExtClassLoader.getExtClassLoader();
    } catch (IOException localIOException1) {
        throw new InternalError("Could not create extension class loader", localIOException1);
    }
    try {
        // 加載應(yīng)用類加載器
        this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
    } catch (IOException localIOException2) {
        throw new InternalError("Could not create application class loader", localIOException2);
    }
    // 設(shè)置AppClassLoader為線程上下文類加載器
    Thread.currentThread().setContextClassLoader(this.loader);
    // ...
    
    static class ExtClassLoader extends java.net.URLClassLoader
    static class AppClassLoader extends java.net.URLClassLoader
}

Launcher初始化了ExtClassLoaderAppClassLoader,并將AppClassLoader設(shè)置為線程上下文類加載器,同時(shí),初始化AppClassLoader時(shí)傳入了ExtClassLoader實(shí)例,WHY? 這里要寫一個(gè)大大的問號(hào)

ExtClassLoaderAppClassLoader都繼承自URLClassLoader,而最終的父類則為ClassLoader。

查看源碼可以得知,初始化AppClassLoader時(shí)傳入的ExtClassLoader實(shí)例最終設(shè)置為了AppClassLoader(ClassLoader)的parent屬性,parent屬性的作用是什么?

父類加載器

每個(gè)類都對(duì)應(yīng)一個(gè)加載它的類加載器,我們可以通過程序來驗(yàn)證

public class ClassLoaderDemo {
    public static void main(String[] args) {
        System.out.println("ClassLodarDemo"s ClassLoader is " + ClassLoaderDemo.class.getClassLoader());
        System.out.println("DNSNameService"s ClassLoader is " + DNSNameService.class.getClassLoader());
        System.out.println("String"s ClassLoader is " + String.class.getClassLoader());
    }
}

輸出為

ClassLodarDemo"s ClassLoader is sun.misc.Launcher$AppClassLoader@135fbaa4
DNSNameService"s ClassLoader is sun.misc.Launcher$ExtClassLoader@6e0be858
String"s ClassLoader is null

ClassLodarDemo為我們自己創(chuàng)建的類,其類加載器為AppClassLoader
DNSNameService為%JRE_HOME%/lib/ext目錄下的類,其類加載器為ExtClassLoader
String存在于rt.jar中,但其類加載器為null,這里是應(yīng)為rt.jar由Bootstrap ClassLoader加載,而Bootstrap ClassLoader是由C++編寫,屬于JVM的一部分

每個(gè)類加載器,都有一個(gè)父類加載器(parent),同樣通過程序驗(yàn)證

public class ClassLoaderDemo {
    public static void main(String[] args) {
        System.out.println("ClassLodarDemo"s ClassLoader is " + ClassLoaderDemo.class.getClassLoader());
        System.out.println("The Parent of ClassLodarDemo"s ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent());
        System.out.println("The GrandParent of ClassLodarDemo"s ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent());
    }
}

輸出為

ClassLodarDemo"s ClassLoader is sun.misc.Launcher$AppClassLoader@135fbaa4
The Parent of ClassLodarDemo"s ClassLoader is sun.misc.Launcher$ExtClassLoader@2503dbd3
The GrandParent of ClassLodarDemo"s ClassLoader is null

AppClassLoader的父類加載器為ExtClassLoader
ExtClassLoader的父類加載器為null,null并不代表ExtClassLoader沒有父類加載器,而是Bootstrap ClassLoader

雙親委派

類加載器在加載類或者其他資源時(shí),使用的是如上圖所示的雙親委派模型,這種模型要求除了頂層的BootStrap ClassLoader外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器(父類加載器不是父類繼承),如果一個(gè)類加載器收到了類加載請(qǐng)求,首先會(huì)把這個(gè)請(qǐng)求委派給父類加載器加載,只有父類加載器無法完成類加載請(qǐng)求時(shí),子類加載器才會(huì)嘗試自己去加載。

要理解雙親委派,可以查看ClassLoader.loadClass方法

protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 檢查是否已經(jīng)加載過
        Class c = findLoadedClass(name);
        if (c == null) { // 沒有被加載過
            long t0 = System.nanoTime();
            // 首先委派給父類加載器加載
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // 如果父類加載器無法加載,才嘗試加載
                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) {
            resolveClass(c);
        }
        return c;
    }
}
破壞雙親委派模型

雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,在java的世界中大部分的類加載器都遵循這個(gè)模型,但也有例外,一個(gè)典型的例子便是JNDI服務(wù)。

JNDI存放于rt.jar,由啟動(dòng)類加載器加載,JNDI的目的是對(duì)資源進(jìn)行集中管理和查找,它需要調(diào)用由各廠商實(shí)現(xiàn)并部署在應(yīng)用程序ClassPath下的JNDI接口實(shí)現(xiàn)的代碼,如此一來,在雙親委派模型下,啟動(dòng)類加載器根本無法加載這些代碼

針對(duì)此問題,java設(shè)計(jì)團(tuán)隊(duì)引入了線程上下文類加載器(Thread Context ClassLoader),這個(gè)類加載器可以通過Thread類的setContextClassLoader進(jìn)行設(shè)置,默認(rèn)繼承父線程類加載器
通過線程上下文類加載器,JNDI服務(wù)通過此類加載器,由父類加載器請(qǐng)求子類加載器完成類加載動(dòng)作

破壞雙親委派模型的例子還有很多,如tomcat服務(wù)、osgi、jigsaw等等,是否破壞雙親委派模型并沒有對(duì)與錯(cuò),只是不同場(chǎng)景下的具體應(yīng)用而已

自定義ClassLoader

不論是AppClassLoader還是ExtClassLoader還是啟動(dòng)類加載器,其加載類的路徑都是固定的,如果我們需要加載外部類或者資源,如某路徑下或網(wǎng)絡(luò)上,這樣便需要自定義類加載器
自定義類加載器,只需要繼承ClassLoader類,復(fù)寫findClass方法,在findClass方法中調(diào)用defineClass方法即可

一個(gè)ClassLoader創(chuàng)建時(shí)如果沒有指定parent,那么它的parent默認(rèn)就是AppClassLoader
如果需要制定一個(gè)ClassLoader的父類加載器為啟動(dòng)類加載器,只需要將其parent指定為null即可

首先,編寫一個(gè)測(cè)試用的類文件

public class BeLoadedClass {
    public void say() {
        System.out.println("I"m Loaded by " + this.getClass().getClassLoader());
    }
}

將其編譯,放入/data/classloader目錄下

接下來,編寫DiskClassLoader

public class DiskClassLoader extends URLClassLoader {

    public DiskClassLoader(URL path) throws MalformedURLException {
        super(new URL[]{path});
    }

    public DiskClassLoader(URL path, ClassLoader parent) throws MalformedURLException {
        super(new URL[]{path}, parent);
    }
}

這里直接繼承URLClassLoader類,該類findClass的實(shí)現(xiàn)如下

protected Class findClass(final String name) throws ClassNotFoundException {
    final Class result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction>() {
                public Class run() throws ClassNotFoundException {
                    // 類文件全路徑
                    String path = name.replace(".", "/").concat(".class");
                    // 指定資源目錄下查找
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            // 調(diào)用defineClass生成類
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

編寫測(cè)試程序

public class ClassLoaderDemo {
    public static void main(String[] args) throws Exception {
        URL path = new File("/data/classloader").toURI().toURL();

        DiskClassLoader diskClassLoaderA = new DiskClassLoader(path);
        Class clazzA = diskClassLoaderA.loadClass("BeLoadedClass");
        Method sayA = clazzA.getMethod("say");
        Object instanceA = clazzA.newInstance();
        sayA.invoke(instanceA);
        System.out.println(diskClassLoaderA);
        System.out.println("clazzA@" + clazzA.hashCode());

        System.out.println("====");

        DiskClassLoader diskClassLoaderB = new DiskClassLoader(path, diskClassLoaderA);
        Class clazzB = diskClassLoaderB.loadClass("BeLoadedClass");
        Method sayB = clazzB.getMethod("say");
        Object instanceB = clazzA.newInstance();
        sayB.invoke(instanceB);
        System.out.println(diskClassLoaderB);
        System.out.println("clazzB@" + clazzB.hashCode());

        System.out.println("====");

        DiskClassLoader diskClassLoaderC = new DiskClassLoader(path);
        Class clazzC = diskClassLoaderC.loadClass("BeLoadedClass");
        Method sayC = clazzC.getMethod("say");
        Object instanceC = clazzC.newInstance();
        sayC.invoke(instanceC);
        System.out.println(diskClassLoaderC);
        System.out.println("clazzC@" + clazzC.hashCode());

        System.out.println("====");

        System.out.println("clazzA == clazzB " + (clazzA == clazzB));
        System.out.println("clazzC == clazzB " + (clazzC == clazzB));
    }
}

輸出為

I"m Loaded by com.manerfan.jvm.oom.DiskClassLoader@4b67cf4d
com.manerfan.jvm.DiskClassLoader@4b67cf4d
clazzA@312714112
====
I"m Loaded by com.manerfan.jvm.oom.DiskClassLoader@4b67cf4d
com.manerfan.jvm.DiskClassLoader@29453f44
clazzB@312714112
====
I"m Loaded by com.manerfan.jvm.oom.DiskClassLoader@5cad8086
com.manerfan.jvm.DiskClassLoader@5cad8086
clazzC@1639705018
====
clazzA == clazzB true
clazzC == clazzB false

這里我們定義了3個(gè)ClassLoader,類搜索路徑均為/data/classloader,其中diskClassLoaderB的父類加載器為diskClassLoaderA
clazzA clazzB clazzC 分別由 diskClassLoaderA diskClassLoaderB diskClassLoaderC 加載
instanceA instanceB instanceA 分別由 clazzA clazzB clazzC 創(chuàng)建

從輸出可以看出,instanceA及instanceB所對(duì)應(yīng)的class均由diskClassLoaderA加載
雖然diskClassLoaderA與diskClassLoaderB為兩個(gè)不同的類加載器,但由于diskClassLoaderB的父類加載器為diskClassLoaderA,從輸出結(jié)果可以看出,clazzA與clazzB完全相同(包括內(nèi)存地址),這也說明clazzA與clazzB均由同一類加載器加載
而instanceC所對(duì)應(yīng)的class,classC卻是由diskClassLoaderC加載

這個(gè)例子,能更好的幫助理解雙親委派模型

類的唯一性

對(duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間

如上例,雖然classA(classB)與classC的類路徑相同,但由于被不同的類加載器加載,其卻屬于兩個(gè)不同的類名稱空間

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68622.html

相關(guān)文章

  • 修煉內(nèi)功】[JVM] 虛擬機(jī)棧及字節(jié)碼基礎(chǔ)

    摘要:本文已收錄修煉內(nèi)功躍遷之路在淺談虛擬機(jī)內(nèi)存模型一文中有簡(jiǎn)單介紹過,虛擬機(jī)棧是線程私有的,每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,方法執(zhí)行時(shí)棧幀入棧,方法結(jié)束時(shí)棧幀出棧,虛擬機(jī)中棧幀的入棧順序就是方法的調(diào)用順序?qū)懥撕芏辔淖郑疾槐M如意,十分慚 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbtSi5?w=1654&h=96...

    VEIGHTZ 評(píng)論0 收藏0
  • 修煉內(nèi)功】[JVM] 類文件結(jié)構(gòu)

    摘要:本文已收錄修煉內(nèi)功躍遷之路學(xué)習(xí)語言的時(shí)候,需要在不同的目標(biāo)操作系統(tǒng)上或者使用交叉編譯環(huán)境,使用正確的指令集編譯成對(duì)應(yīng)操作系統(tǒng)可運(yùn)行的執(zhí)行文件,才可以在相應(yīng)的系統(tǒng)上運(yùn)行,如果使用操作系統(tǒng)差異性的庫或者接口,還需要針對(duì)不同的系統(tǒng)做不同的處理宏的 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbtpPd?w=2065&h=11...

    Eminjannn 評(píng)論0 收藏0
  • 修煉內(nèi)功】[JVM] 淺談虛擬機(jī)內(nèi)存模型

    摘要:也正是因此,一旦出現(xiàn)內(nèi)存泄漏或溢出問題,如果不了解的內(nèi)存管理原理,那么將會(huì)對(duì)問題的排查帶來極大的困難。 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbsP9I?w=1024&h=580); 不論做技術(shù)還是做業(yè)務(wù),對(duì)于Java開發(fā)人員來講,理解JVM各種原理的重要性不必再多言 對(duì)于C/C++而言,可以輕易地操作任意地址的...

    sanyang 評(píng)論0 收藏0
  • 修煉內(nèi)功】[Java8] Lambda究竟是不是匿名類的語法糖

    摘要:本文已收錄修煉內(nèi)功躍遷之路初次接觸的時(shí)候感覺表達(dá)式很神奇表達(dá)式帶來的編程新思路,但又總感覺它就是匿名類或者內(nèi)部類的語法糖而已,只是語法上更為簡(jiǎn)潔罷了,如同以下的代碼匿名類內(nèi)部類編譯后會(huì)產(chǎn)生三個(gè)文件雖然從使用效果來看,與匿名類或者內(nèi)部類有相 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbui4o?w=800&h=600)...

    ?xiaoxiao, 評(píng)論0 收藏0
  • 修煉內(nèi)功】[JVM] 虛擬機(jī)視角的方法調(diào)用

    摘要:本文已收錄修煉內(nèi)功躍遷之路我們寫的方法在被編譯為文件后是如何被虛擬機(jī)執(zhí)行的對(duì)于重寫或者重載的方法,是在編譯階段就確定具體方法的么如果不是,虛擬機(jī)在運(yùn)行時(shí)又是如何確定具體方法的方法調(diào)用不等于方法執(zhí)行,一切方法調(diào)用在文件中都只是常量池中的符號(hào)引 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbuesq?w=2114&h=12...

    shevy 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<