1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【37期】請(qǐng)你詳細(xì)說說類加載流程,類加載機(jī)制及自定義類加載器

        共 12208字,需瀏覽 25分鐘

         ·

        2020-09-17 04:51

        程序員的成長(zhǎng)之路
        互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
        關(guān)注


        閱讀本文大概需要 12?分鐘。

        來自:juejin.im/post/5cffa528e51d4556da53d091

        一、引言

        當(dāng)程序使用某個(gè)類時(shí),如果該類還未被加載到內(nèi)存中,則JVM會(huì)通過加載、鏈接、初始化三個(gè)步驟對(duì)該類進(jìn)行類加載。

        二、類的加載、鏈接、初始化

        1、加載

        類加載指的是將類的class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)java.lang.Class對(duì)象。類的加載過程是由類加載器來完成,類加載器由JVM提供。我們開發(fā)人員也可以通過繼承ClassLoader來實(shí)現(xiàn)自己的類加載器。
        1.1、加載的class來源
        • 從本地文件系統(tǒng)內(nèi)加載class文件

        • 從JAR包加載class文件

        • 通過網(wǎng)絡(luò)加載class文件

        • 把一個(gè)java源文件動(dòng)態(tài)編譯,并執(zhí)行加載。

        2、類的鏈接

        通過類的加載,內(nèi)存中已經(jīng)創(chuàng)建了一個(gè)Class對(duì)象。鏈接負(fù)責(zé)將二進(jìn)制數(shù)據(jù)合并到 JRE中。鏈接需要通過驗(yàn)證、準(zhǔn)備、解析三個(gè)階段。
        2.1、驗(yàn)證
        驗(yàn)證階段用于檢查被加載的類是否有正確的內(nèi)部結(jié)構(gòu),并和其他類協(xié)調(diào)一致。即是否滿足java虛擬機(jī)的約束。

        2.2、準(zhǔn)備

        類準(zhǔn)備階段負(fù)責(zé)為類的類變量分配內(nèi)存,并設(shè)置默認(rèn)初始值。
        2.3、解析
        我們知道,引用其實(shí)對(duì)應(yīng)于內(nèi)存地址。思考這樣一個(gè)問題,在編寫代碼時(shí),使用引用,方法時(shí),類知道這些引用方法的內(nèi)存地址嗎?顯然是不知道的,因?yàn)轭愡€未被加載到虛擬機(jī)中,你無法獲得這些地址。
        舉例來說,對(duì)于一個(gè)方法的調(diào)用,編譯器會(huì)生成一個(gè)包含目標(biāo)方法所在的類、目標(biāo)方法名、接收參數(shù)類型以及返回值類型的符號(hào)引用,來指代要調(diào)用的方法。
        解析階段的目的,就是將這些符號(hào)引用解析為實(shí)際引用。如果符號(hào)引用指向一個(gè)未被加載的類,或者未被加載類的字段或方法,那么解析將觸發(fā)這個(gè)類的加載(但未必會(huì)觸發(fā)解析與初始化)。

        3、類的初始化

        類的初始化階段,虛擬機(jī)主要對(duì)類變量進(jìn)行初始化。虛擬機(jī)調(diào)用< clinit>方法,進(jìn)行類變量的初始化。
        java類中對(duì)類變量進(jìn)行初始化的兩種方式:
        1. 在定義時(shí)初始化

        2. 在靜態(tài)初始化塊內(nèi)初始化

        3.1、< clinit>方法相關(guān)
        虛擬機(jī)會(huì)收集類及父類中的類變量及類方法組合為< clinit>方法,根據(jù)定義的順序進(jìn)行初始化。虛擬機(jī)會(huì)保證子類的< clinit>執(zhí)行之前,父類的< clinit>方法先執(zhí)行完畢。

        因此,虛擬機(jī)中第一個(gè)被執(zhí)行完畢的< clinit>方法肯定是java.lang.Object方法。

        public?class?Test?{
        ????static?int?A?=?10;
        ????static?{
        ????????A?=?20;
        ????}
        }

        class?Test1?extends?Test?{
        ????private?static?int?B?=?A;
        ????public?static?void?main(String[]?args)?{
        ????????System.out.println(Test1.B);
        ????}
        }
        //輸出結(jié)果
        //20
        從輸出中看出,父類的靜態(tài)初始化塊在子類靜態(tài)變量初始化之前初始化完畢,所以輸出結(jié)果是20,不是10。
        如果類或者父類中都沒有靜態(tài)變量及方法,虛擬機(jī)不會(huì)為其生成< clinit>方法。
        接口與類不同的是,執(zhí)行接口的<clinit>方法不需要先執(zhí)行父接口的<clinit>方法。只有當(dāng)父接口中定義的變量使用時(shí),父接口才會(huì)初始化。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<clinit>方法。
        public?interface?InterfaceInitTest?{
        ????long?A?=?CurrentTime.getTime();

        }

        interface?InterfaceInitTest1?extends?InterfaceInitTest?{
        ????int?B?=?100;
        }

        class?InterfaceInitTestImpl?implements?InterfaceInitTest1?{
        ????public?static?void?main(String[]?args)?{
        ????????System.out.println(InterfaceInitTestImpl.B);
        ????????System.out.println("---------------------------");
        ????????System.out.println("當(dāng)前時(shí)間:"+InterfaceInitTestImpl.A);
        ????}
        }

        class?CurrentTime?{
        ????static?long?getTime()?{
        ????????System.out.println("加載了InterfaceInitTest接口");
        ????????return?System.currentTimeMillis();
        ????}
        }
        //輸出結(jié)果
        //100
        //---------------------------
        //加載了InterfaceInitTest接口
        //當(dāng)前時(shí)間:1560158880660
        從輸出驗(yàn)證了:對(duì)于接口,只有真正使用父接口的類變量才會(huì)真正的加載父接口。這跟普通類加載不一樣。
        虛擬機(jī)會(huì)保證一個(gè)類的< clinit>方法在多線程環(huán)境中被正確地加鎖和同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只有一個(gè)線程去執(zhí)行這個(gè)類的< clinit>方法,其他線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行< clinit>方法完畢。

        public?class?MultiThreadInitTest?{
        ????static?int?A?=?10;
        ????static?{
        ???????????System.out.println(Thread.currentThread()+"init?MultiThreadInitTest");
        ????????try?{
        ????????????TimeUnit.SECONDS.sleep(10);
        ????????}?catch?(InterruptedException?e)?{
        ????????????e.printStackTrace();
        ????????}
        ????}

        ????public?static?void?main(String[]?args)?{
        ????????Runnable?runnable?=?()?->?{
        ????????????System.out.println(Thread.currentThread()?+?"start");
        ????????????System.out.println(MultiThreadInitTest.A);
        ????????????System.out.println(Thread.currentThread()?+?"run?over");
        ????????};
        ????????Thread?thread1?=?new?Thread(runnable);
        ????????Thread?thread2?=?new?Thread(runnable);
        ????????thread1.start();
        ????????thread2.start();
        ????}
        }
        //輸出結(jié)果
        //Thread[main,5,main]init?MultiThreadInitTest
        //Thread[Thread-0,5,main]start
        //10
        //Thread[Thread-0,5,main]run?over
        //Thread[Thread-1,5,main]start
        //10
        //Thread[Thread-1,5,main]run?over
        從輸出中看出驗(yàn)證了:只有第一個(gè)線程對(duì)MultiThreadInitTest進(jìn)行了一次初始化,第二個(gè)線程一直阻塞等待等第一個(gè)線程初始化完畢。
        3.2、類初始化時(shí)機(jī)
        1. 當(dāng)虛擬機(jī)啟動(dòng)時(shí),初始化用戶指定的主類;

        2. 當(dāng)遇到用以新建目標(biāo)類實(shí)例的new指令時(shí),初始化new指令的目標(biāo)類;

        3. 當(dāng)遇到調(diào)用靜態(tài)方法或者使用靜態(tài)變量,初始化靜態(tài)變量或方法所在的類;

        4. 子類初始化過程會(huì)觸發(fā)父類初始化;

        5. 如果一個(gè)接口定義了default方法,那么直接實(shí)現(xiàn)或者間接實(shí)現(xiàn)該接口的類的初始化,會(huì)觸發(fā)該接口初始化;

        6. 使用反射API對(duì)某個(gè)類進(jìn)行反射調(diào)用時(shí),初始化這個(gè)類;

        7. Class.forName()會(huì)觸發(fā)類的初始化

        3.3、final定義的初始化
        注意:對(duì)于一個(gè)使用final定義的常量,如果在編譯時(shí)就已經(jīng)確定了值,在引用時(shí)不會(huì)觸發(fā)初始化,因?yàn)樵诰幾g的時(shí)候就已經(jīng)確定下來,就是“宏變量”。如果在編譯時(shí)無法確定,在初次使用才會(huì)導(dǎo)致初始化。
        public?class?StaticInnerSingleton?{
        ????/**
        ?????*?使用靜態(tài)內(nèi)部類實(shí)現(xiàn)單例:
        ?????* 1:線程安全
        ?????* 2:懶加載
        ?????* 3:非反序列化安全,即反序列化得到的對(duì)象與序列化時(shí)的單例對(duì)象不是同一個(gè),違反單例原則
        ?????*/

        ????private?static?class?LazyHolder?{
        ????????private?static?final?StaticInnerSingleton?INNER_SINGLETON?=?new?StaticInnerSingleton();
        ????}

        ????private?StaticInnerSingleton()?{
        ????}

        ????public?static?StaticInnerSingleton?getInstance()?{
        ????????return?LazyHolder.INNER_SINGLETON;
        ????}
        }
        看這個(gè)例子,單例模式靜態(tài)內(nèi)部類實(shí)現(xiàn)方式。我們可以看到單例實(shí)例使用final定義,但在編譯時(shí)無法確定下來,所以在第一次使用StaticInnerSingleton.getInstance()方法時(shí),才會(huì)觸發(fā)靜態(tài)內(nèi)部類的加載,也就是延遲加載。
        這里想指出,如果final定義的變量在編譯時(shí)無法確定,則在使用時(shí)還是會(huì)進(jìn)行類的初始化。
        3.4、ClassLoader只會(huì)對(duì)類進(jìn)行加載,不會(huì)進(jìn)行初始化
        public?class?Tester?{
        ????static?{
        ????????System.out.println("Tester類的靜態(tài)初始化塊");
        ????}
        }

        class?ClassLoaderTest?{
        ????public?static?void?main(String[]?args)?throws?ClassNotFoundException?{
        ????????ClassLoader?classLoader?=?ClassLoader.getSystemClassLoader();
        ????????//下面語句僅僅是加載Tester類
        ????????classLoader.loadClass("loader.Tester");
        ????????System.out.println("系統(tǒng)加載Tester類");
        ????????//下面語句才會(huì)初始化Tester類
        ????????Class.forName("loader.Tester");
        ????}
        }
        //輸出結(jié)果
        //系統(tǒng)加載Tester類
        //Tester類的靜態(tài)初始化塊
        從輸出證明:ClassLoader只會(huì)對(duì)類進(jìn)行加載,不會(huì)進(jìn)行初始化;使用Class.forName()會(huì)強(qiáng)制導(dǎo)致類的初始化。

        三、類加載器

        類加載器負(fù)責(zé)將.class文件(不管是jar,還是本地磁盤,還是網(wǎng)絡(luò)獲取等等)加載到內(nèi)存中,并為之生成對(duì)應(yīng)的java.lang.Class對(duì)象。一個(gè)類被加載到JVM中,就不會(huì)第二次加載了。
        那怎么判斷是同一個(gè)類呢?
        每個(gè)類在JVM中使用全限定類名(包名+類名)與類加載器聯(lián)合為唯一的ID,所以如果同一個(gè)類使用不同的類加載器,可以被加載到虛擬機(jī),但彼此不兼容。

        1、JVM類加載器分類

        1.1、Bootstrap ClassLoader
        Bootstrap ClassLoader為根類加載器,負(fù)責(zé)加載java的核心類庫(kù)。根加載器不是ClassLoader的子類,是有C++實(shí)現(xiàn)的。
        public?class?BootstrapTest?{
        ????public?static?void?main(String[]?args)?{
        ????????//獲取根類加載器所加載的全部URL數(shù)組
        ????????URL[]?urLs?=?Launcher.getBootstrapClassPath().getURLs();
        ????????Arrays.stream(urLs).forEach(System.out::println);
        ????}
        }
        //輸出結(jié)果
        //file:/C:/SorftwareInstall/java/jdk/jre/lib/resources.jar
        //file:/C:/SorftwareInstall/java/jdk/jre/lib/rt.jar
        //file:/C:/SorftwareInstall/java/jdk/jre/lib/sunrsasign.jar
        //file:/C:/SorftwareInstall/java/jdk/jre/lib/jsse.jar
        //file:/C:/SorftwareInstall/java/jdk/jre/lib/jce.jar
        //file:/C:/SorftwareInstall/java/jdk/jre/lib/charsets.jar
        //file:/C:/SorftwareInstall/java/jdk/jre/lib/jfr.jar
        //file:/C:/SorftwareInstall/java/jdk/jre/classes
        根類加載器負(fù)責(zé)加載%JAVA_HOME%/jre/lib下的jar包(以及由虛擬機(jī)參數(shù) -Xbootclasspath 指定的類)。
        我們將rt.jar解壓,可以看到我們經(jīng)常使用的類庫(kù)就在這個(gè)jar包中。
        1.2 、Extension ClassLoader
        Extension ClassLoader為擴(kuò)展類加載器,負(fù)責(zé)加載%JAVA_HOME%/jre/ext或者java.ext.dirs系統(tǒng)熟悉指定的目錄的jar包。大家可以將自己寫的工具包放到這個(gè)目錄下,可以方便自己使用。
        1.3、 System ClassLoader
        System ClassLoader為系統(tǒng)(應(yīng)用)類加載器,負(fù)責(zé)加載加載來自java命令的-classpath選項(xiàng)、java.class.path系統(tǒng)屬性,或者CLASSPATH環(huán)境變量所指定的JAR包和類路徑。程序可以通過ClassLoader.getSystemClassLoader()來獲取系統(tǒng)類加載器。如果沒有特別指定,則用戶自定義的類加載器默認(rèn)都以系統(tǒng)類加載器作為父加載器。

        四、類加載機(jī)制

        1.1、JVM主要的類加載機(jī)制。

        1. 全盤負(fù)責(zé)當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí),該Class所依賴和引用的其他Class也由該類加載器負(fù)責(zé)載入,除非顯示使用另一個(gè)類加載器來載入。

        2. 父類委托(雙親委派)先讓父加載器試圖加載該Class,只有在父加載器無法加載時(shí)該類加載器才會(huì)嘗試從自己的類路徑中加載該類。

        3. 緩存機(jī)制緩存機(jī)制會(huì)將已經(jīng)加載的class緩存起來,當(dāng)程序中需要使用某個(gè)Class時(shí),類加載器先從緩存區(qū)中搜尋該Class,只有當(dāng)緩存中不存在該Class時(shí),系統(tǒng)才會(huì)讀取該類的二進(jìn)制數(shù)據(jù),并將其轉(zhuǎn)換為Class對(duì)象,存入緩存中。這就是為什么更改了class后,需要重啟JVM才生效的原因。

        注意:類加載器之間的父子關(guān)系并不是類繼承上的父子關(guān)系,而是實(shí)例之間的父子關(guān)系。

        public?class?ClassloaderPropTest?{
        ????public?static?void?main(String[]?args)?throws?IOException?{
        ????????//獲取系統(tǒng)類加載器
        ????????ClassLoader?systemClassLoader?=?ClassLoader.getSystemClassLoader();
        ????????System.out.println("系統(tǒng)類加載器:"?+?systemClassLoader);
        ????????/*
        ????????獲取系統(tǒng)類加載器的加載路徑——通常由CLASSPATH環(huán)境變量指定,如果操作系統(tǒng)沒有指定
        ????????CLASSPATH環(huán)境變量,則默認(rèn)以當(dāng)前路徑作為系統(tǒng)類加載器的加載路徑
        ?????????*/

        ????????Enumeration?eml?=?systemClassLoader.getResources("");
        ????????while?(eml.hasMoreElements())?{
        ????????????System.out.println(eml.nextElement());
        ????????}
        ????????//獲取系統(tǒng)類加載器的父類加載器,得到擴(kuò)展類加載器
        ????????ClassLoader?extensionLoader?=?systemClassLoader.getParent();
        ????????System.out.println("系統(tǒng)類的父加載器是擴(kuò)展類加載器:"?+?extensionLoader);
        ????????System.out.println("擴(kuò)展類加載器的加載路徑:"?+?System.getProperty("java.ext.dirs"));
        ????????System.out.println("擴(kuò)展類加載器的parant:"?+?extensionLoader.getParent());
        ????}
        }
        //輸出結(jié)果
        //系統(tǒng)類加載器:sun.misc.Launcher$AppClassLoader@18b4aac2
        //file:/C:/ProjectTest/FengKuang/out/production/FengKuang/
        //系統(tǒng)類的父加載器是擴(kuò)展類加載器:sun.misc.Launcher$ExtClassLoader@1540e19d
        //擴(kuò)展類加載器的加載路徑:C:\SorftwareInstall\java\jdk\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
        //擴(kuò)展類加載器的parant:null
        從輸出中驗(yàn)證了:系統(tǒng)類加載器的父加載器是擴(kuò)展類加載器。但輸出中擴(kuò)展類加載器的父加載器是null,這是因?yàn)楦讣虞d器不是java實(shí)現(xiàn)的,是C++實(shí)現(xiàn)的,所以獲取不到。但擴(kuò)展類加載器的父加載器是根加載器。

        1.2、類加載流程圖



        圖中紅色部分,可以是我們自定義實(shí)現(xiàn)的類加載器來進(jìn)行加載。

        五、創(chuàng)建并使用自定義類加載器

        1、自定義類加載分析

        除了根類加載器,所有類加載器都是ClassLoader的子類。所以我們可以通過繼承ClassLoader來實(shí)現(xiàn)自己的類加載器。
        ClassLoader類有兩個(gè)關(guān)鍵的方法:
        1. protected Class?loadClass(String name, boolean resolve)name為類名,resove如果為true,在加載時(shí)解析該類。

        2. protected Class?findClass(String name)?:根據(jù)指定類名來查找類。

        所以,如果要實(shí)現(xiàn)自定義類,可以重寫這兩個(gè)方法來實(shí)現(xiàn)。但推薦重寫findClass方法,而不是重寫loadClass方法,因?yàn)閘oadClass方法內(nèi)部會(huì)調(diào)用findClass方法。
        我們來看一下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?{
        ????????????????//第二步,判斷父加載器是否為null
        ????????????????????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)?{
        ???????????????????//第三步,如果前面都沒有找到,就會(huì)調(diào)用findClass方法
        ????????????????????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;
        ????????}
        ????}
        loadClass加載方法流程:
        1. 判斷此類是否已經(jīng)加載;

        2. 如果父加載器不為null,則使用父加載器進(jìn)行加載;反之,使用根加載器進(jìn)行加載;

        3. 如果前面都沒加載成功,則使用findClass方法進(jìn)行加載。

        所以,為了不影響類的加載過程,我們重寫findClass方法即可簡(jiǎn)單方便的實(shí)現(xiàn)自定義類加載。

        2、實(shí)現(xiàn)自定義類加載器

        基于以上分析,我們簡(jiǎn)單重寫findClass方法進(jìn)行自定義類加載。
        public?class?Hello?{
        ???public?void?test(String?str){
        ???????System.out.println(str);
        ???}
        }

        public?class?MyClassloader?extends?ClassLoader?{

        ????/**
        ?????*?讀取文件內(nèi)容
        ?????*
        ?????*?@param?fileName?文件名
        ?????*?@return
        ?????*/

        ????private?byte[]?getBytes(String?fileName)?throws?IOException?{
        ????????File?file?=?new?File(fileName);
        ????????long?len?=?file.length();
        ????????byte[]?raw?=?new?byte[(int)?len];
        ????????try?(FileInputStream?fin?=?new?FileInputStream(file))?{
        ????????????//一次性讀取Class文件的全部二進(jìn)制數(shù)據(jù)
        ????????????int?read?=?fin.read(raw);
        ????????????if?(read?!=?len)?{
        ????????????????throw?new?IOException("無法讀取全部文件");
        ????????????}
        ????????????return?raw;
        ????????}
        ????}

        ????@Override
        ????protected?Class?findClass(String?name)?throws?ClassNotFoundException?{
        ????????Class?clazz?=?null;
        ????????//將包路徑的(.)替換為斜線(/)
        ????????String?fileStub?=?name.replace(".",?"/");
        ????????String?classFileName?=?fileStub?+?".class";
        ????????File?classFile?=?new?File(classFileName);

        ????????//如果Class文件存在,系統(tǒng)負(fù)責(zé)將該文件轉(zhuǎn)換為Class對(duì)象
        ????????if?(classFile.exists())?{
        ????????????try?{
        ????????????????//將Class文件的二進(jìn)制數(shù)據(jù)讀入數(shù)組
        ????????????????byte[]?raw?=?getBytes(classFileName);
        ????????????????//調(diào)用ClassLoader的defineClass方法將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為Class對(duì)象
        ????????????????clazz?=?defineClass(name,?raw,?0,?raw.length);
        ????????????}?catch?(IOException?e)?{
        ????????????????e.printStackTrace();
        ????????????}
        ????????}
        ????????//如果clazz為null,表明加載失敗,拋出異常
        ????????if?(null?==?clazz)?{
        ????????????throw?new?ClassNotFoundException(name);
        ????????}
        ????????return?clazz;
        ????}

        ????public?static?void?main(String[]?args)?throws?Exception?{
        ????????String?classPath?=?"loader.Hello";
        ????????MyClassloader?myClassloader?=?new?MyClassloader();
        ????????Class?aClass?=?myClassloader.loadClass(classPath);
        ????????Method?main?=?aClass.getMethod("test",?String.class);
        ????????System.out.println(main);
        ????????main.invoke(aClass.newInstance(),?"Hello?World");
        ????}
        }
        //輸出結(jié)果
        //Hello?World
        ClassLoader還有一個(gè)重要的方法defineClass(String name, byte[] b, int off, int len)。此方法的作用是將class的二進(jìn)制數(shù)組轉(zhuǎn)換為Calss對(duì)象。
        此例子很簡(jiǎn)單,我寫了一個(gè)Hello測(cè)試類,并且編譯過后放在了當(dāng)前路徑下(大家可以在findClass中加入判斷,如果沒有此文件,可以嘗試查找.java文件,并進(jìn)行編譯得到.class文件;或者判斷.java文件的最后更新時(shí)間大于.class文件最后更新時(shí)間,再進(jìn)行重新編譯等邏輯)。

        六、總結(jié)

        本篇從類加載的三大階段:加載、鏈接、初始化開始細(xì)說每個(gè)階段的過程;詳細(xì)講解了JVM常用的類加載器的區(qū)別與聯(lián)系,以及類加載機(jī)制流程,最后通過自定義的類加載器例子結(jié)束本篇。小弟能力有限,大家看出有問題請(qǐng)指出,讓博主學(xué)習(xí)改正。歡迎討論啊。
        注意:本篇博客總結(jié)主要來源。如有轉(zhuǎn)載,請(qǐng)注明出處
        1. 《瘋狂java講義(第3版)》

        2. 《深入理解java虛擬機(jī)++JVM高級(jí)特性與最佳實(shí)踐》

        推薦閱讀:

        【36期】說說 如何停止一個(gè)正在運(yùn)行的線程?

        【35期】談?wù)勀銓?duì)Java線程之間通信方式的理解

        【34期】談?wù)劄槭裁匆鸱謹(jǐn)?shù)據(jù)庫(kù)?有哪些方法?

        5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號(hào)內(nèi)回復(fù)「2048」,即可免費(fèi)獲?。?!

        微信掃描二維碼,關(guān)注我的公眾號(hào)

        朕已閱?

        瀏覽 43
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            自慰 免费观看 | 欧美性猛交 自制 | 7777人妻精品无码视频 | 亂伦国产一区二区三区电影 | 一级婬片AAAAAAAAHD | 97精品伊人久久久大香线蕉97 | 超色一区视频 | 动漫美女打扑克软件 | 波多野结衣伦理电影在线观看 | 艹逼电影网站 |