真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

一文讓你理解Class類加載機(jī)制

一文讓你理解Class類加載機(jī)制

創(chuàng)新互聯(lián)公司技術(shù)團(tuán)隊(duì)10多年來(lái)致力于為客戶提供成都網(wǎng)站建設(shè)、成都網(wǎng)站制作、品牌網(wǎng)站建設(shè)、全網(wǎng)營(yíng)銷推廣、搜索引擎SEO優(yōu)化等服務(wù)。經(jīng)過(guò)多年發(fā)展,公司擁有經(jīng)驗(yàn)豐富的技術(shù)團(tuán)隊(duì),先后服務(wù)、推廣了近千家網(wǎng)站,包括各類中小企業(yè)、企事單位、高校等機(jī)構(gòu)單位。

理解類加載機(jī)制

Class文件是各種編譯器編譯生成的二進(jìn)制文件,在Class文件中描述了各種與該類相關(guān)的信息,但是Class文件本身是一個(gè)靜態(tài)的東西,想要使用某個(gè)類的話,需要java虛擬機(jī)將該類對(duì)應(yīng)的Class文件加載進(jìn)虛擬機(jī)中之后才能進(jìn)行運(yùn)行和使用。

舉個(gè)例子,Class文件就好比是各個(gè)玩具設(shè)計(jì)商提供的設(shè)計(jì)方案,這些方案本身是不能直接給小朋友玩的,需要玩具生產(chǎn)商根據(jù)方案的相關(guān)信息制造出具體的玩具才可以給小朋友玩。那么不同的設(shè)計(jì)商有他們自己的設(shè)計(jì)思路,只要最終設(shè)計(jì)出來(lái)的方案符合生產(chǎn)商生產(chǎn)的要求即可。生產(chǎn)商在生產(chǎn)玩具時(shí),首先會(huì)根據(jù)自己的生產(chǎn)標(biāo)準(zhǔn)對(duì)設(shè)計(jì)商提交來(lái)的方案進(jìn)行閱讀,審核,校驗(yàn)等一系列步驟,如果該方案符合生產(chǎn)標(biāo)準(zhǔn),則會(huì)根據(jù)方案創(chuàng)建出對(duì)應(yīng)的模具,當(dāng)經(jīng)銷商需要某個(gè)玩具時(shí),生產(chǎn)商則拿出對(duì)應(yīng)的模具生產(chǎn)出具體的玩具,然后把玩具提交給經(jīng)銷商。

對(duì)于java而言,虛擬機(jī)就是玩具生產(chǎn)商,設(shè)計(jì)商提交過(guò)來(lái)的方案就是一個(gè)個(gè)的Class文件,方案創(chuàng)建的模具就
總的來(lái)說(shuō),類的加載過(guò)程,包括卸載在內(nèi)的整個(gè)生命周期共有以下7個(gè)階段:

一文讓你理解Class類加載機(jī)制

加載、驗(yàn)證、準(zhǔn)備、初始化、卸載這5個(gè)階段的順序是確定的,但是解析階段不一定,在某些情況下解析可以在初始化之后再執(zhí)行,為了支持java的運(yùn)行時(shí)綁定,也成為動(dòng)態(tài)綁定或晚期綁定。invokedynamic指令就是用于動(dòng)態(tài)語(yǔ)言支持,這里“動(dòng)態(tài)”的含義是必須等到城市實(shí)際運(yùn)行到這條指令的時(shí)候,解析動(dòng)作才開(kāi)始執(zhí)行。

加載

“加載”是“類加載”過(guò)程中的一個(gè)階段,在加載階段,虛擬機(jī)需要做以下3件事情:

  • 通過(guò)類的全限定名獲得該類的二進(jìn)制字節(jié)流

  • 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換成方法區(qū)中的某個(gè)運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

  • 在方法區(qū)內(nèi)存(對(duì)于HotSpot虛擬機(jī))中生成一個(gè)代表該類的java.lang.Class對(duì)象,作為訪問(wèn)方法區(qū)中該類的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)的外部接口

加載階段中“通過(guò)類的全限定名獲得該類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作,被放到j(luò)ava虛擬機(jī)外部實(shí)現(xiàn),目的是最大限度的讓應(yīng)用程序去決定該如何獲取所需的類,而實(shí)現(xiàn)該動(dòng)作的代碼模塊就是類加載器(ClassLoader),JVM提供了3種類加載器:

  • 啟動(dòng)類加載器(Bootstrap ClassLoader):負(fù)責(zé)加載 JAVAHOME\lib 目錄中的,或通過(guò)-Xbootclasspath參數(shù)指定路徑中的,且被虛擬機(jī)認(rèn)可(按文件名識(shí)別,如rt.jar)的類。

  • 擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)加載 JAVAHOME\lib\ext 目錄中的,或通過(guò)java.ext.dirs系統(tǒng)變量指定路徑中的類庫(kù)。

  • 應(yīng)用程序類加載器(Application ClassLoader):負(fù)責(zé)加載用戶路徑(classpath)上的類庫(kù)。

加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)中了。

驗(yàn)證

加載完成后,緊接著(更確切的說(shuō)是交叉執(zhí)行)虛擬機(jī)會(huì)對(duì)加載的字節(jié)流進(jìn)行驗(yàn)證。虛擬機(jī)如果不檢查輸入的字節(jié)流,對(duì)其安全信任的話,很可能會(huì)因?yàn)檩d入了有害的字節(jié)流而導(dǎo)致系統(tǒng)崩潰。驗(yàn)證階段大致會(huì)完成4中不同的檢驗(yàn)動(dòng)作:

文件格式驗(yàn)證

文件格式驗(yàn)證主要是校驗(yàn)該字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)所接受。這個(gè)階段包括但不限于以下驗(yàn)證點(diǎn):

  • 是否以魔數(shù)0xCAFEBABE開(kāi)頭
  • 主、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理的范圍之內(nèi)
  • 常量池中是否有不支持的常量類型(通過(guò)tag校驗(yàn))
  • 常量的索引是否有指向不存在或不符合類型的常量
  • ...

元數(shù)據(jù)驗(yàn)證

元數(shù)據(jù)驗(yàn)證主要是對(duì)字節(jié)流中的描述信息(描述符)進(jìn)行語(yǔ)義分析,以確保其描述的信息符合java語(yǔ)言規(guī)范的要求。這個(gè)階段包括但不限于以下驗(yàn)證點(diǎn):

  • 這個(gè)類是否有父類,除了java.lang.Object,所有的類都應(yīng)該有父類
  • 這個(gè)類的父類是否繼承了不允許被繼承的類,如被final修飾的類
  • 如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了父類或接口中要求的所有的方法
  • ...

字節(jié)碼驗(yàn)證

字節(jié)碼驗(yàn)證主要是對(duì)類的方法體進(jìn)行分析,確保在方法運(yùn)行時(shí)不會(huì)有危害虛擬機(jī)的事件發(fā)生。這個(gè)階段包括但不限于以下驗(yàn)證點(diǎn):

  • 操作數(shù)棧的數(shù)據(jù)類型與指令碼中所需類型是否相符
  • 校驗(yàn)跳轉(zhuǎn)指令是否會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上
  • 校驗(yàn)方法體中類型轉(zhuǎn)換是否是有效的
  • ...

符號(hào)引用驗(yàn)證

符號(hào)引用驗(yàn)證主要是對(duì)類自身以外的信息進(jìn)行匹配新校驗(yàn),包括常量池中的各種符號(hào)引用。這個(gè)階段包括但不限于以下驗(yàn)證點(diǎn):

  • 符號(hào)引用中通過(guò)字符串描述的全限定類名是否能找到對(duì)應(yīng)的類
  • 在指定的類中是否存在符合方法的字段描述符以及簡(jiǎn)單名稱所描述的字段和方法
  • ...

準(zhǔn)備

準(zhǔn)備階段是為類變量(static)在方法區(qū)中分配內(nèi)存并設(shè)置初始值(默認(rèn)值,如int的默認(rèn)值為0)的階段。實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在java堆中。為類變量設(shè)置初始值跟該變量是否有final修飾符有關(guān)系。 如果沒(méi)用final進(jìn)行修飾,如下列的代碼:

// 準(zhǔn)備階段執(zhí)行完成后,value變量的值為int的“零值”,即:0
// 把value賦值為10的putstatic指令是程序被編譯后,
// 存放于類構(gòu)造器()方法中的,所以具體賦值的操作會(huì)在初始化階段執(zhí)行
public static int value = 10;

如果使用了final進(jìn)行修飾,如下列的代碼:

// 如果類字段的字段屬性表中有ConstantValue屬性,
// 則會(huì)在準(zhǔn)備階段將變量的值初始化為ConstantValue所指定的值
// 準(zhǔn)備階段執(zhí)行完成后,VALUE變量的值被賦值為20
public final static int VALUE = 20;

解析

解析階段是虛擬機(jī)將常量池中的符號(hào)引用替換為直接引用的過(guò)程,在該階段將會(huì)進(jìn)行符號(hào)引用的校驗(yàn)。 符號(hào)引用是Class文件中用來(lái)描述所引用目標(biāo)的符號(hào),可以是任何形式的字面量。 直接引用是虛擬機(jī)在內(nèi)存中引用具體類或接口的,可以是直接指向目標(biāo)的指針,相對(duì)偏移量或者是一個(gè)能間接定位到目標(biāo)的句柄。 簡(jiǎn)單的來(lái)說(shuō),符號(hào)引用是Class類文件用來(lái)定位目標(biāo)的,直接引用是虛擬機(jī)用來(lái)在內(nèi)存中定位目標(biāo)的。

一文讓你理解Class類加載機(jī)制

初始化

初始化階段是執(zhí)行類的構(gòu)造器方法()的過(guò)程,類的構(gòu)造方法由類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊按照在源文件中出現(xiàn)的順序合并而成,該合并操作由編譯器完成。

  • ()方法對(duì)于類或接口不是必須的,如果一個(gè)類中沒(méi)有靜態(tài)代碼塊,也沒(méi)有靜態(tài)變量的賦值操作,那么編譯器不會(huì)生成()方法
  • ()方法與實(shí)例構(gòu)造器方法()不同,不需要顯式的調(diào)用父類的方法,虛擬機(jī)會(huì)保證父類的優(yōu)先執(zhí)行
  • 為了防止多次執(zhí)行,虛擬機(jī)會(huì)確保方法在多線程環(huán)境下被正確的加鎖同步執(zhí)行,如果有多個(gè)線程同時(shí)初始化一個(gè)類,那么只有一個(gè)線程能夠執(zhí)行方法,其它線程進(jìn)行阻塞等待,直到執(zhí)行完成
  • 執(zhí)行接口的方法是不需要先執(zhí)行父接口的,只有使用父接口中定義的變量時(shí),才會(huì)執(zhí)行。

java虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有一下5中情況必須立即對(duì)類進(jìn)行初始化:

  1. 遇到new,getstatic,putstatic,invokestatic這4條字節(jié)碼指令時(shí),生成這4條指令的最常見(jiàn)的java代碼場(chǎng)景是:使用new實(shí)例化對(duì)象時(shí),讀取或設(shè)置類的靜態(tài)字段時(shí),調(diào)用一個(gè)類的靜態(tài)方法時(shí)
  2. 使用java.lang.reflect對(duì)類進(jìn)行反射調(diào)用時(shí),如通過(guò)Class.forName()創(chuàng)建對(duì)象時(shí)
  3. 當(dāng)初始化一個(gè)類時(shí),如果父類還沒(méi)有初始化,則要先觸發(fā)父類的初始化,即先要執(zhí)行父類的構(gòu)造器方法()
  4. 啟動(dòng)虛擬機(jī)時(shí),需要初始化包含main方法的類
  5. 在JDK1.7中,如果java.lang.invoke.MethodHandler實(shí)例最后的解析結(jié)果REFgetStatic、REFputStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄對(duì)應(yīng)的類沒(méi)有進(jìn)行初始化

以下幾種情況,不會(huì)觸發(fā)類初始化:

  • 通過(guò)子類引用父類的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化,而不會(huì)觸發(fā)子類的初始化。
    class Parent {
    static int a = 100;    
    static   {        
        System.out.println("parent init!");    
    }
    }
    class Child extends Parent {    
    static {        
        System.out.println("child init!");    
    }
    }
    public class Init{      
    public static void main(String[] args){  
        // 只會(huì)執(zhí)行父類的初始化,不會(huì)執(zhí)行子類的初始化
        // 將打印:parent init!
        System.out.println(Child.a);      
    }
    }
  • 定義對(duì)象數(shù)組,不會(huì)觸發(fā)該類的初始化。
    public class Init{      
    public static void main(String[] args){ 
        // 不會(huì)有任何輸出
        Parent[] parents = new Parent[10];    
    }  
    }
  • 常量在編譯期間會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒(méi)有直接引用定義常量的類,不會(huì)觸發(fā)定義常量所在的類的初始化。
    class Const {    
    static final int A = 100;    
    static {        
        System.out.println("Const init");    
    }
    }
    public class Init{      
    public static void main(String[] args){ 
        // Const.A會(huì)存入Init類的常量池中,調(diào)用時(shí)并不會(huì)觸發(fā)Const類的初始化
        // 將打?。?00
        System.out.println(Const.A);      
    }  
    }
  • 通過(guò)類名獲取Class對(duì)象,不會(huì)觸發(fā)類的初始化。
    class Cat {    
    private string name;    
    static {        
        System.out.println("Cat is loaded");    
    }
    }
    public class Init{      
    public static void main(String[] args){ 
        // 不會(huì)打印任何信息
        Class catClazz = Class.class;      
    }  
    }
  • 通過(guò)Class.forName加載指定類時(shí),如果指定參數(shù)initialize為false時(shí),也不會(huì)觸發(fā)類初始化,其實(shí)這個(gè)參數(shù)是告訴虛擬機(jī),是否要對(duì)類進(jìn)行初始化。
    class Cat {    
    private string name;    
    static {        
        System.out.println("Cat is loaded");    
    }
    }
    public class Init{      
    public static void main(String[] args) throws ClassNotFoundException{ 
        // 不會(huì)打印任何信息
        Class catClazz = Class.forName("com.test.Cat",false,Cat.class.getClassLoader());      
    }  
    }
  • 通過(guò)ClassLoader默認(rèn)的loadClass方法,也不會(huì)觸發(fā)初始化動(dòng)作
    class Cat {    
    private string name;    
    static {        
        System.out.println("Cat is loaded");    
    }
    }
    public class Init{      
    public static void main(String[] args) throws ClassNotFoundException{ 
        // 不會(huì)打印任何信息
        new ClassLoader(){}.loadClass("com.test.Cat");      
    }  
    }

    最后,附上一幅Class類加載過(guò)程的思維導(dǎo)圖:

一文讓你理解Class類加載機(jī)制

是Class文件加載進(jìn)虛擬機(jī)中的類,生產(chǎn)的玩具就是類的實(shí)例對(duì)象。

因此,從Class文件到對(duì)象需要經(jīng)過(guò)的步驟大致為: Class文件-->類-->實(shí)例對(duì)象 而類的加載機(jī)制,就是負(fù)責(zé)將Class文件轉(zhuǎn)換成虛擬機(jī)中的類的一個(gè)過(guò)程。

總的來(lái)說(shuō),類的加載過(guò)程,包括卸載在內(nèi)的整個(gè)生命周期共有以下7個(gè)階段:

一文讓你理解Class類加載機(jī)制

加載、驗(yàn)證、準(zhǔn)備、初始化、卸載這5個(gè)階段的順序是確定的,但是解析階段不一定,在某些情況下解析可以在初始化之后再執(zhí)行,為了支持java的運(yùn)行時(shí)綁定,也成為動(dòng)態(tài)綁定或晚期綁定。invokedynamic指令就是用于動(dòng)態(tài)語(yǔ)言支持,這里“動(dòng)態(tài)”的含義是必須等到城市實(shí)際運(yùn)行到這條指令的時(shí)候,解析動(dòng)作才開(kāi)始執(zhí)行。

加載

“加載”是“類加載”過(guò)程中的一個(gè)階段,在加載階段,虛擬機(jī)需要做以下3件事情:

  • 通過(guò)類的全限定名獲得該類的二進(jìn)制字節(jié)流

  • 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換成方法區(qū)中的某個(gè)運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)

  • 在方法區(qū)內(nèi)存(對(duì)于HotSpot虛擬機(jī))中生成一個(gè)代表該類的java.lang.Class對(duì)象,作為訪問(wèn)方法區(qū)中該類的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)的外部接口

加載階段中“通過(guò)類的全限定名獲得該類的二進(jìn)制字節(jié)流”這個(gè)動(dòng)作,被放到j(luò)ava虛擬機(jī)外部實(shí)現(xiàn),目的是最大限度的讓應(yīng)用程序去決定該如何獲取所需的類,而實(shí)現(xiàn)該動(dòng)作的代碼模塊就是類加載器(ClassLoader),JVM提供了3種類加載器:

  • 啟動(dòng)類加載器(Bootstrap ClassLoader):負(fù)責(zé)加載 JAVAHOME\lib 目錄中的,或通過(guò)-Xbootclasspath參數(shù)指定路徑中的,且被虛擬機(jī)認(rèn)可(按文件名識(shí)別,如rt.jar)的類。?

  • 擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)加載 JAVAHOME\lib\ext 目錄中的,或通過(guò)java.ext.dirs系統(tǒng)變量指定路徑中的類庫(kù)。?

  • 應(yīng)用程序類加載器(Application ClassLoader):負(fù)責(zé)加載用戶路徑(classpath)上的類庫(kù)。

加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)中了。

驗(yàn)證

加載完成后,緊接著(更確切的說(shuō)是交叉執(zhí)行)虛擬機(jī)會(huì)對(duì)加載的字節(jié)流進(jìn)行驗(yàn)證。虛擬機(jī)如果不檢查輸入的字節(jié)流,對(duì)其安全信任的話,很可能會(huì)因?yàn)檩d入了有害的字節(jié)流而導(dǎo)致系統(tǒng)崩潰。驗(yàn)證階段大致會(huì)完成4中不同的檢驗(yàn)動(dòng)作:

文件格式驗(yàn)證?

文件格式驗(yàn)證主要是校驗(yàn)該字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)所接受。這個(gè)階段包括但不限于以下驗(yàn)證點(diǎn):

  • 是否以魔數(shù)0xCAFEBABE開(kāi)頭
  • 主、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理的范圍之內(nèi)
  • 常量池中是否有不支持的常量類型(通過(guò)tag校驗(yàn))
  • 常量的索引是否有指向不存在或不符合類型的常量
  • ...

元數(shù)據(jù)驗(yàn)證?

元數(shù)據(jù)驗(yàn)證主要是對(duì)字節(jié)流中的描述信息(描述符)進(jìn)行語(yǔ)義分析,以確保其描述的信息符合java語(yǔ)言規(guī)范的要求。這個(gè)階段包括但不限于以下驗(yàn)證點(diǎn):

  • 這個(gè)類是否有父類,除了java.lang.Object,所有的類都應(yīng)該有父類
  • 這個(gè)類的父類是否繼承了不允許被繼承的類,如被final修飾的類
  • 如果這個(gè)類不是抽象類,是否實(shí)現(xiàn)了父類或接口中要求的所有的方法
  • ...

字節(jié)碼驗(yàn)證?

字節(jié)碼驗(yàn)證主要是對(duì)類的方法體進(jìn)行分析,確保在方法運(yùn)行時(shí)不會(huì)有危害虛擬機(jī)的事件發(fā)生。這個(gè)階段包括但不限于以下驗(yàn)證點(diǎn):

  • 操作數(shù)棧的數(shù)據(jù)類型與指令碼中所需類型是否相符
  • 校驗(yàn)跳轉(zhuǎn)指令是否會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上
  • 校驗(yàn)方法體中類型轉(zhuǎn)換是否是有效的
  • ...

符號(hào)引用驗(yàn)證?

符號(hào)引用驗(yàn)證主要是對(duì)類自身以外的信息進(jìn)行匹配新校驗(yàn),包括常量池中的各種符號(hào)引用。這個(gè)階段包括但不限于以下驗(yàn)證點(diǎn):

  • 符號(hào)引用中通過(guò)字符串描述的全限定類名是否能找到對(duì)應(yīng)的類
  • 在指定的類中是否存在符合方法的字段描述符以及簡(jiǎn)單名稱所描述的字段和方法
  • ...

準(zhǔn)備

準(zhǔn)備階段是為類變量(static)在方法區(qū)中分配內(nèi)存并設(shè)置初始值(默認(rèn)值,如int的默認(rèn)值為0)的階段。實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在java堆中。為類變量設(shè)置初始值跟該變量是否有final修飾符有關(guān)系。 如果沒(méi)用final進(jìn)行修飾,如下列的代碼:

// 準(zhǔn)備階段執(zhí)行完成后,value變量的值為int的“零值”,即:0
// 把value賦值為10的putstatic指令是程序被編譯后,
// 存放于類構(gòu)造器()方法中的,所以具體賦值的操作會(huì)在初始化階段執(zhí)行
public static int value = 10;

如果使用了final進(jìn)行修飾,如下列的代碼:

// 如果類字段的字段屬性表中有ConstantValue屬性,
// 則會(huì)在準(zhǔn)備階段將變量的值初始化為ConstantValue所指定的值
// 準(zhǔn)備階段執(zhí)行完成后,VALUE變量的值被賦值為20
public final static int VALUE = 20;

解析

解析階段是虛擬機(jī)將常量池中的符號(hào)引用替換為直接引用的過(guò)程,在該階段將會(huì)進(jìn)行符號(hào)引用的校驗(yàn)。 符號(hào)引用是Class文件中用來(lái)描述所引用目標(biāo)的符號(hào),可以是任何形式的字面量。 直接引用是虛擬機(jī)在內(nèi)存中引用具體類或接口的,可以是直接指向目標(biāo)的指針,相對(duì)偏移量或者是一個(gè)能間接定位到目標(biāo)的句柄。 簡(jiǎn)單的來(lái)說(shuō),符號(hào)引用是Class類文件用來(lái)定位目標(biāo)的,直接引用是虛擬機(jī)用來(lái)在內(nèi)存中定位目標(biāo)的。

一文讓你理解Class類加載機(jī)制

初始化

初始化階段是執(zhí)行類的構(gòu)造器方法()的過(guò)程,類的構(gòu)造方法由類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊按照在源文件中出現(xiàn)的順序合并而成,該合并操作由編譯器完成。

  • ()方法對(duì)于類或接口不是必須的,如果一個(gè)類中沒(méi)有靜態(tài)代碼塊,也沒(méi)有靜態(tài)變量的賦值操作,那么編譯器不會(huì)生成()方法
  • ()方法與實(shí)例構(gòu)造器方法()不同,不需要顯式的調(diào)用父類的方法,虛擬機(jī)會(huì)保證父類的優(yōu)先執(zhí)行
  • 為了防止多次執(zhí)行,虛擬機(jī)會(huì)確保方法在多線程環(huán)境下被正確的加鎖同步執(zhí)行,如果有多個(gè)線程同時(shí)初始化一個(gè)類,那么只有一個(gè)線程能夠執(zhí)行方法,其它線程進(jìn)行阻塞等待,直到執(zhí)行完成
  • 執(zhí)行接口的方法是不需要先執(zhí)行父接口的,只有使用父接口中定義的變量時(shí),才會(huì)執(zhí)行。

java虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且只有一下5中情況必須立即對(duì)類進(jìn)行初始化:

  1. 遇到new,getstatic,putstatic,invokestatic這4條字節(jié)碼指令時(shí),生成這4條指令的最常見(jiàn)的java代碼場(chǎng)景是:使用new實(shí)例化對(duì)象時(shí),讀取或設(shè)置類的靜態(tài)字段時(shí),調(diào)用一個(gè)類的靜態(tài)方法時(shí)
  2. 使用java.lang.reflect對(duì)類進(jìn)行反射調(diào)用時(shí),如通過(guò)Class.forName()創(chuàng)建對(duì)象時(shí)
  3. 當(dāng)初始化一個(gè)類時(shí),如果父類還沒(méi)有初始化,則要先觸發(fā)父類的初始化,即先要執(zhí)行父類的構(gòu)造器方法()
  4. 啟動(dòng)虛擬機(jī)時(shí),需要初始化包含main方法的類
  5. 在JDK1.7中,如果java.lang.invoke.MethodHandler實(shí)例最后的解析結(jié)果REFgetStatic、REFputStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄對(duì)應(yīng)的類沒(méi)有進(jìn)行初始化

以下幾種情況,不會(huì)觸發(fā)類初始化:

  • 通過(guò)子類引用父類的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化,而不會(huì)觸發(fā)子類的初始化。
    class Parent {
    static int a = 100;    
    static   {        
        System.out.println("parent init!");    
    }
    }
    class Child extends Parent {    
    static {        
        System.out.println("child init!");    
    }
    }
    public class Init{      
    public static void main(String[] args){  
        // 只會(huì)執(zhí)行父類的初始化,不會(huì)執(zhí)行子類的初始化
        // 將打?。簆arent init!
        System.out.println(Child.a);      
    }
    }
  • 定義對(duì)象數(shù)組,不會(huì)觸發(fā)該類的初始化。
    public class Init{      
    public static void main(String[] args){ 
        // 不會(huì)有任何輸出
        Parent[] parents = new Parent[10];    
    }  
    }
  • 常量在編譯期間會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒(méi)有直接引用定義常量的類,不會(huì)觸發(fā)定義常量所在的類的初始化。
    class Const {    
    static final int A = 100;    
    static {        
        System.out.println("Const init");    
    }
    }
    public class Init{      
    public static void main(String[] args){ 
        // Const.A會(huì)存入Init類的常量池中,調(diào)用時(shí)并不會(huì)觸發(fā)Const類的初始化
        // 將打?。?00
        System.out.println(Const.A);      
    }  
    }
  • 通過(guò)類名獲取Class對(duì)象,不會(huì)觸發(fā)類的初始化。
    class Cat {    
    private string name;    
    static {        
        System.out.println("Cat is loaded");    
    }
    }
    public class Init{      
    public static void main(String[] args){ 
        // 不會(huì)打印任何信息
        Class catClazz = Class.class;      
    }  
    }
  • 通過(guò)Class.forName加載指定類時(shí),如果指定參數(shù)initialize為false時(shí),也不會(huì)觸發(fā)類初始化,其實(shí)這個(gè)參數(shù)是告訴虛擬機(jī),是否要對(duì)類進(jìn)行初始化。
    class Cat {    
    private string name;    
    static {        
        System.out.println("Cat is loaded");    
    }
    }
    public class Init{      
    public static void main(String[] args) throws ClassNotFoundException{ 
        // 不會(huì)打印任何信息
        Class catClazz = Class.forName("com.test.Cat",false,Cat.class.getClassLoader());      
    }  
    }
  • 通過(guò)ClassLoader默認(rèn)的loadClass方法,也不會(huì)觸發(fā)初始化動(dòng)作
    class Cat {    
    private string name;    
    static {        
        System.out.println("Cat is loaded");    
    }
    }
    public class Init{      
    public static void main(String[] args) throws ClassNotFoundException{ 
        // 不會(huì)打印任何信息
        new ClassLoader(){}.loadClass("com.test.Cat");      
    }  
    }

    最后,附上一幅Class類加載過(guò)程的思維導(dǎo)圖:

一文讓你理解Class類加載機(jī)制


網(wǎng)站標(biāo)題:一文讓你理解Class類加載機(jī)制
網(wǎng)址分享:http://weahome.cn/article/gcheec.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部