欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java虛擬機(jī)之對(duì)象創(chuàng)建過(guò)程與類加載機(jī)制及雙親委派模型

 更新時(shí)間:2021年11月18日 11:20:39   作者:張維鵬  
這篇文章主要給大家介紹了關(guān)于Java虛擬機(jī)之對(duì)象創(chuàng)建過(guò)程與類加載機(jī)制及雙親委派模型的相關(guān)資料,本文通過(guò)示例代碼以及圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

一、對(duì)象的創(chuàng)建過(guò)程:

 1、對(duì)象的創(chuàng)建過(guò)程:

對(duì)象的創(chuàng)建過(guò)程一般是從 new 指令(JVM層面)開(kāi)始的,整個(gè)創(chuàng)建過(guò)程如下:

(1)首先檢查 new 指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用;

(2)如果沒(méi)有,說(shuō)明類還沒(méi)有被加載,則須先執(zhí)行相應(yīng)的類加載、解析和初始化等類加載過(guò)程,該過(guò)程的具體步驟詳見(jiàn)下文

(3)如果有,虛擬機(jī)將在堆中為新生對(duì)象分配內(nèi)存。分配內(nèi)存方式有:指針碰撞和空閑列表,具體選擇哪種分配方式由 Java 堆是否規(guī)整決定,而 Java 堆是否規(guī)整又由所采用的垃圾收集器是否帶有壓縮整理功能決定。

指針碰撞:如果Java堆是絕對(duì)規(guī)整的,所有用過(guò)的內(nèi)存都放在一邊,所有沒(méi)用過(guò)的內(nèi)存存放在另一邊,中間存放一個(gè)指針作為分界點(diǎn)指示器。分配內(nèi)存時(shí),將指針從用過(guò)的內(nèi)存區(qū)域向空閑內(nèi)存區(qū)域移動(dòng)等距離區(qū)域。
空閑列表:如果Java不是規(guī)整的,這時(shí),虛擬機(jī)就必須維護(hù)一張列表,列表上記錄了可用的內(nèi)存塊,在分配內(nèi)存時(shí),從列表上找到一個(gè)足夠大的連續(xù)內(nèi)存塊分配給對(duì)象,并更新列表上的記錄。

在分配對(duì)象內(nèi)存空間的過(guò)程中,需要考慮在并發(fā)情況下,線程是否安全的問(wèn)題。因?yàn)閯?chuàng)建對(duì)象在虛擬機(jī)中是非常頻繁的行為,可能出現(xiàn)正在給對(duì)象A分配內(nèi)存,指針還沒(méi)來(lái)得及修改,對(duì)象B又同時(shí)使用了原來(lái)的指針來(lái)分配內(nèi)存的情況。因此必須要保證線程安全,解決這個(gè)問(wèn)題有兩種方案:

  • CAS以及失敗重試(比較和交換機(jī)制):對(duì)分配內(nèi)存空間的操作進(jìn)行同步處理,實(shí)際上虛擬機(jī)采用CAS配上失敗重試的方式保證更新操作的原子性。CAS操作需要輸入兩個(gè)數(shù)值,一個(gè)舊值(操作前期望的值)和一個(gè)新值,在操作期間先比較舊值有沒(méi)有發(fā)送變化,如果沒(méi)有變化,才交換成新值,否則不進(jìn)行交換。
  • TLAB(Thread Local Allocation Buffer,本地線程分配緩存):把內(nèi)存分配的動(dòng)作按照線程劃分在不同的空間之中進(jìn)行,即每個(gè)線程在Java堆中預(yù)先分配一小塊私有內(nèi)存,也就是本地線程分配緩沖。TLAB的目的是在為新對(duì)象分配內(nèi)存空間時(shí),讓每個(gè)Java應(yīng)用線程能在使用自己專屬的分配指針來(lái)分配空間,減少同步開(kāi)銷。

(4)內(nèi)存分配完成后,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零值,保證了對(duì)象實(shí)例的字段在 Java 代碼中可以不賦初始值就可以直接使用;

(5)對(duì)對(duì)象進(jìn)行必要的設(shè)置,例如是哪個(gè)對(duì)象的實(shí)例、如何才能找到類元信息、對(duì)象的哈希碼、GC 分代年齡等信息,這些信息存放在對(duì)象頭中。

(6)執(zhí)行 init 方法,把對(duì)象按照程序員意愿進(jìn)行初始化。

至此,一個(gè)對(duì)象就被創(chuàng)建完畢,同時(shí)會(huì)在Java棧中分配一個(gè)引用指向這個(gè)對(duì)象,通過(guò)棧上面的引用可以訪問(wèn)堆中的具體對(duì)象,訪問(wèn)對(duì)象主要有兩種方式:通過(guò)句柄訪問(wèn)對(duì)象和直接指針訪問(wèn)對(duì)象。

2、對(duì)象的訪問(wèn)方式:

(1)通過(guò)句柄訪問(wèn)對(duì)象:

在Java堆中劃出一塊內(nèi)存專門作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自的地址地址信息。

(2)通過(guò)直接指針訪問(wèn)對(duì)象:

(3)優(yōu)劣對(duì)比:

   ① 使用句柄,reference中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而reference本身不需要修改。

   ② 直接指針,速度快,節(jié)省一次指定定位的開(kāi)銷。

二、類加載機(jī)制:

Java 文件中的代碼在編譯后,就會(huì)生成JVM能夠識(shí)別的二進(jìn)制字節(jié)流 class 文件,class 文件中描述的各種信息,都需要加載到虛擬機(jī)中才能被運(yùn)行和使用。

類加載機(jī)制,就是虛擬機(jī)把類的數(shù)據(jù)從class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校檢,轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型的過(guò)程。類從加載到虛擬機(jī)內(nèi)存開(kāi)始,到卸載出內(nèi)存結(jié)束,整個(gè)生命周期包括七個(gè)階段,如下圖(每個(gè)過(guò)程作用見(jiàn)文章第四部分):

2.1、加載階段:

這階段的虛擬機(jī)需要完成三件事情:

(1)通過(guò)一個(gè)類的全限定名來(lái)獲取定義此類的二進(jìn)制字節(jié)流。
(2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
(3)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問(wèn)入口。

2.2、驗(yàn)證階段:

這階段是為了確保class文件的字節(jié)流包含的信息符合當(dāng)前虛擬機(jī)的要求,不會(huì)危害虛擬機(jī)自身的安全。分為4個(gè)校檢動(dòng)作:

(1)文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理,通過(guò)該階段后,字節(jié)流會(huì)進(jìn)入內(nèi)存的方法區(qū)中進(jìn)行儲(chǔ)存。
(2)元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)言分析,對(duì)類的元數(shù)據(jù)信息進(jìn)行語(yǔ)義校驗(yàn),確保其描述的信息符合java語(yǔ)言規(guī)范要求。
(3)字節(jié)碼驗(yàn)證:通過(guò)數(shù)據(jù)流和控制流分析,確定程序語(yǔ)義是合法的、符合邏輯的。這個(gè)階段對(duì)類的方法進(jìn)行校驗(yàn)分析,保證類的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的事件。
(4)符號(hào)引用驗(yàn)證:對(duì)類自身以外的信息(常量池中各種符號(hào)引用)的信息進(jìn)行校檢,確保解析動(dòng)作能正常執(zhí)行(該動(dòng)作發(fā)生在解析階段中)

2.3、準(zhǔn)備階段:

正式為類變量分配內(nèi)存空間并設(shè)置數(shù)據(jù)類型零值。這個(gè)階段進(jìn)行內(nèi)存分配的僅包括類變量(static修飾),不包括實(shí)例變量,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨對(duì)象一起分配在java堆。

public static int value= 123 ; //變量value在準(zhǔn)備階段過(guò)后的初始值是0,不是123.
public static final int value = 123 ; //特殊情況:會(huì)生成ConstantValue屬性,在準(zhǔn)備階段初始值是123.

final、static、static final修飾的字段賦值的區(qū)別:

(1)static修飾的字段在準(zhǔn)備階段被初始化為0或null等默認(rèn)值,然后在初始化階段(觸發(fā)類構(gòu)造器)才會(huì)被賦予代碼中設(shè)定的值,如果沒(méi)有設(shè)定值,那么它的值就為默認(rèn)值。

(2)static final修飾的字段在Javac時(shí)生成ConstantValue屬性,在類加載的準(zhǔn)備階段根據(jù)ConstantValue的值為該字段賦值,它沒(méi)有默認(rèn)值,必須顯式地賦值,否則Javac時(shí)會(huì)報(bào)錯(cuò)??梢岳斫鉃樵诰幾g期即把結(jié)果放入了常量池中。

(3)final修飾的字段在運(yùn)行時(shí)被初始化(可以直接賦值,也可以在實(shí)例構(gòu)造器中()賦值),一旦賦值便不可更改。

2.4、解析階段:

將常量池的符號(hào)引用替換為直接引用的過(guò)程。解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型、方法句柄、和調(diào)用限定符7類符號(hào)引用。

對(duì)同一符號(hào)引用進(jìn)行多次解析請(qǐng)求是很常見(jiàn)的事情,除invokedynamic指令以外,虛擬機(jī)可以對(duì)第一次解析的結(jié)果進(jìn)行緩存,從而避免解析動(dòng)作重復(fù)進(jìn)行。invokedynamic對(duì)應(yīng)的引用稱為“動(dòng)態(tài)調(diào)用限定符”,必須等到程序?qū)嶋H運(yùn)行到這條指定的時(shí)候,解析動(dòng)作才能進(jìn)行。因此,當(dāng)碰到由前面的invokedynamic指令觸發(fā)過(guò)的解析的符號(hào)引用時(shí),并不意味著這個(gè)解析結(jié)果對(duì)其他的invokedynamic指令也同樣生效。

  (1)符號(hào)引用:符號(hào)引用是以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何字面量,只要使用時(shí)無(wú)歧義定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局可以不相同,但是他們能接受的符號(hào)引用必須都是一致的,符號(hào)引用的字面量形式明確地定義在java虛擬機(jī)規(guī)范的calss文件格式中。

  (2)直接引用:直接引用是可以直接定位到目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位目標(biāo)的句柄。直接引用是與虛擬機(jī)的內(nèi)存布局相關(guān)的,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。

2.5、初始化:

初始化階段才真正執(zhí)行類中定義的java代碼。執(zhí)行類構(gòu)造器()方法的過(guò)程,并按程序的設(shè)置去初始類變量和其他資源。

2.5.1、類的主動(dòng)引用:

在初始化階段,有且只有5種場(chǎng)景必須立即對(duì)類進(jìn)行“初始化”,稱為主動(dòng)引用:

    (1)遇到new、getstatic、putstatic、invokestatic這4條指定時(shí)。對(duì)應(yīng)的場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候,讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已經(jīng)在編譯期把結(jié)果放入常量池的靜態(tài)字段除外),以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。

    (2)使用java.lang.reflect包的方法對(duì)類進(jìn)行發(fā)射調(diào)用的時(shí)候。

    (3)當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)進(jìn)行初始化,則必須對(duì)父類進(jìn)行初始化。(與接口的區(qū)別:接口在初始化時(shí),并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時(shí)候,才會(huì)初始化)

    (4)當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶指定的要執(zhí)行的主類(包含main方法的類)。

    (5)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化,則需要觸發(fā)其初始化。

2.5.2、類的被動(dòng)引用:

除了主動(dòng)引用,其他引用類的方式都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用:

(1)對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,通過(guò)其子類來(lái)引用父類中定義的靜態(tài)字段,只會(huì)觸發(fā)其父類的初始化而不會(huì)觸發(fā)子類的初始化。

//父類  
public class SuperClass {  
    //靜態(tài)變量value  
    public static int value =123456;  
    //靜態(tài)塊,父類初始化時(shí)會(huì)調(diào)用  
    static{  
        System.out.println("父類初始化!");  
    }  
}  
  
//子類  
public class SubClass extends SuperClass{  
    //靜態(tài)塊,子類初始化時(shí)會(huì)調(diào)用  
    static{  
        System.out.println("子類初始化!");  
    }  
}  
  
//主類、測(cè)試類  
public class InitTest {  
    public static void main(String[] args){  
        System.out.println(SubClass.value);  //輸出結(jié)果:父類初始化! 123456
    }  
}

(2)通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化。

//父類  
public class SuperClass {  
    //靜態(tài)變量value  
    public static int value = 123456;  
    //靜態(tài)塊,父類初始化時(shí)會(huì)調(diào)用  
    static{  
        System.out.println("父類初始化!");  
    }  
}  
  
//主類、測(cè)試類  
public class InitTest {  
    public static void main(String[] args){  
        SuperClass[] test = new SuperClass[10]; //輸出結(jié)果:沒(méi)有任何輸出結(jié)果
    }  
}

(3)常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒(méi)有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化。

//定義常量的類  
public class ConstClass {  
    static{  
        System.out.println("常量類初始化!");  
    }  
      
    public static final String HELLOWORLD = "hello world!";  
}  
  
//主類、測(cè)試類  
public class InitTest {  
    public static void main(String[] args){  
      System.out.println(ConstClass.HELLOWORLD);  //輸出結(jié)果:hello world
    }  
}  

2.5.3、()方法的特點(diǎn): 

(1)()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊中的語(yǔ)句合并產(chǎn)生的,收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序決定的。靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量,定義在它之后的變量,在前面的靜態(tài)語(yǔ)句塊中可以賦值,但是不可以訪問(wèn)。

public class Test {
	
	static{
		i=0; //給變量賦值可以正常編譯通過(guò)
		System.out.println(i); //編譯器會(huì)提示非法向前引用
	}
	static int i=1;
}

    (2)()方法與實(shí)例構(gòu)造器()方法不同,它不需要顯示調(diào)用父類構(gòu)造器,虛擬機(jī)會(huì)保證子類的()方法執(zhí)行之前,父類的()方法已經(jīng)執(zhí)行完畢,所以父類中定義的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的變量賦值操作,虛擬機(jī)中第一個(gè)被執(zhí)行的()方法的類是java.lang.Object。

    (3)()方法對(duì)于類或接口并不是必需的,如果一個(gè)類中沒(méi)有靜態(tài)語(yǔ)句塊,也就沒(méi)有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類生成()方法。

    (4)接口中不能使用靜態(tài)語(yǔ)句塊,仍然有變量初始化操作,因此仍然會(huì)生成()方法,與類不同的是,執(zhí)行接口中的()方法不需要先執(zhí)行父接口的()方法。只有父接口中定義的變量被使用是,才需要初始化父接口,同時(shí),接口實(shí)現(xiàn)類在初始化時(shí)也不會(huì)執(zhí)行接口的()方法。

    (5)()方法在多線程環(huán)境中被正確的加鎖、同步,多個(gè)線程同時(shí)去初始化一個(gè)類,只會(huì)有一個(gè)線程執(zhí)行()方法,其他線程則需要阻塞等待,直到活動(dòng)線程執(zhí)行()方法完畢,活動(dòng)線程執(zhí)行完畢后,其他線程喚醒后被不會(huì)再次進(jìn)入()方法,因?yàn)橥粋€(gè)類加載器下,一個(gè)類型只會(huì)被初始化一次。

三、類加載器與雙親委派模型:

 3.1、JVM 的類加載器:

類加載機(jī)制生命周期的第一階段,即加載階段需要由類加載器來(lái)完成的,類加載器根據(jù)一個(gè)類的全限定名讀取類的二進(jìn)制字節(jié)流到JVM中,然后生成對(duì)應(yīng)的java.lang.Class對(duì)象實(shí)例,

在虛擬機(jī)默認(rèn)提供了3種類加載器,啟動(dòng)類加載器(Bootstrap ClassLoader)、擴(kuò)展類加載器(Extension ClassLoader)、應(yīng)用類加載器(Application ClassLoader),如果有必要還可以加入自己定義的類加載器。

(1)啟動(dòng)類加載器(Bootstrap ClassLoader):負(fù)責(zé)加載 在\lib目錄 和 被-Xbootclasspath參數(shù)所指定的路徑中的類庫(kù) 

(2)擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)加載 \lib\ext目錄 和 被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù)

(3)應(yīng)用程序類加載器(Application ClassLoader):負(fù)責(zé)加載用戶類路徑classPath所指定的類庫(kù),如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。

(4)自定義加載器(CustomClassLoader):由應(yīng)用程序根據(jù)自身需要自定義,如 Tomcat、Jboss 都會(huì)根據(jù) j2ee 規(guī)范自行實(shí)現(xiàn)。

任意一個(gè)類在 JVM 中的唯一性,是由加載它的類加載器和類的全限定名一共同確定的。因此,比較兩個(gè)類是否“相等”的前提是這兩個(gè)類是由同一個(gè)類加載器加載的,否則,即使兩個(gè)類來(lái)源于同一個(gè) Class 文件,被同一個(gè)虛擬機(jī)加載,只要加載他們的類加載器不同,那這兩個(gè)類就必定不相等。JVM 的類加載機(jī)制,規(guī)定一個(gè)類有且只有一個(gè)類加載器對(duì)它進(jìn)行加載。而如何保證這個(gè)只有一個(gè)類加載器對(duì)它進(jìn)行加載呢?則是由雙親委派模型來(lái)實(shí)現(xiàn)的。

3.2、雙親委派模型:

雙親委派模型要求除了頂層的啟動(dòng)類加載器外,其余類加載器都應(yīng)該有自己的父類加載器。(類加載器之間的父子關(guān)系不是以繼承的關(guān)系實(shí)現(xiàn),而是使用組合關(guān)系來(lái)復(fù)用父加載器的代碼)

3.2.1、雙親委派模型的工作原理:

如果一個(gè)類加載器收到了類加載的請(qǐng)求,他首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層級(jí)的類加載器都是如此,因此所有請(qǐng)求最終都會(huì)被傳到最頂層的啟動(dòng)類加載器中,只有當(dāng)父類加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。因此,加載過(guò)程可以看成自底向上檢查類是否已經(jīng)加載,然后自頂向下加載類。

3.2.2、雙親委派模型的優(yōu)點(diǎn):

(1)使用雙親委派模型來(lái)組織類加載器之間的關(guān)系,Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。

(2)避免類的重復(fù)加載,當(dāng)父類加載器已經(jīng)加載了該類時(shí),子類加載器就沒(méi)必要再加載一次。

(3)解決各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問(wèn)題,越基礎(chǔ)的類由越上層的加載器進(jìn)行加載。避免Java核心API中的類被隨意替換,規(guī)避風(fēng)險(xiǎn),防止核心API庫(kù)被隨意篡改。

例如類 java.lang.Object,它存在在 rt.jar 中,無(wú)論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的 Bootstrap ClassLoader 進(jìn)行加載,因此 Object 類在程序的各種類加載器環(huán)境中都是同一個(gè)類。相反,如果沒(méi)有雙親委派模型而是由各個(gè)類加載器自行加載的話,如果用戶編寫了一個(gè) java.lang.Object 的同名類并放在 ClassPath 中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的 Object 類,程序?qū)⒒靵y。因此,如果開(kāi)發(fā)者嘗試編寫一個(gè)與 rt.jar 類庫(kù)中重名的 Java 類,可以正常編譯,但是永遠(yuǎn)無(wú)法被加載運(yùn)行。

3.3、類加載器源碼:loadClass()

實(shí)現(xiàn)雙親委派的代碼都集中在 java.lang.ClassLoader 的 loadClass() 方法之中,下面我們簡(jiǎn)單看下 loadClass() 的源碼是怎么樣的:

public abstract class ClassLoader {
  // 每個(gè)類加載器都有一個(gè)父加載器
  private final ClassLoader parent;
    
  public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
            // 首先,檢查該類是否已經(jīng)加載過(guò)了
            Class<?> c = findLoadedClass(name);
            // 如果沒(méi)有加載過(guò)
            if (c == null) {
                if (parent != null) {
                 // 先委托給父加載器去加載,注意這是個(gè)遞歸調(diào)用
                 c = parent.loadClass(name, false);
                } else {
                   // 如果父加載器為空,查找 Bootstrap 加載器是不是加載過(guò)了
                   c = findBootstrapClassOrNull(name);
                }
              
                // 如果父加載器沒(méi)加載成功,調(diào)用自己的 findClass 去加載
                if (c == null) {        
                    c = findClass(name);
                }
            } 
        
            return c;
        }
    }
 
    // ClassLoader 中 findClass 方式需要被子類覆蓋,下面這段代碼就是對(duì)應(yīng)代碼
    protected Class<?> findClass(String name){
       //1. 根據(jù)傳入的類名 name,到在特定目錄下去尋找類文件,把 .class 文件讀入內(nèi)存
          ...
       //2. 調(diào)用 defineClass 將字節(jié)數(shù)組轉(zhuǎn)成 Class 對(duì)象
       return defineClass(buf, off, len);
    }
 
    // 將字節(jié)碼數(shù)組解析成一個(gè) Class 對(duì)象,用 native 方法實(shí)現(xiàn)
    protected final Class<?> defineClass(byte[] b, int off, int len){
    }   
}

3.4、如何破壞雙親委派模型:

雙親委派過(guò)程是在 loadClass() 方法中實(shí)現(xiàn)的,如果想要破壞這種機(jī)制,那么就自定義一個(gè)類加載器(繼承自 ClassLoader),重寫其中的 loadClass() 方法,使其不進(jìn)行雙親委派即可。ClassLoader 中和類加載有關(guān)的方法有很多,除了前面提到了 loadClass(),除此之外,還有 findClass() 和 defineClass() 等,那么這幾個(gè)方法有什么區(qū)別呢:

  • loadClass() 就是主要進(jìn)行類加載的方法,默認(rèn)的雙親委派機(jī)制就實(shí)現(xiàn)在這個(gè)方法中
  • findClass() 根據(jù)名稱或位置加載 .class 字節(jié)碼
  • definclass() 把字節(jié)碼轉(zhuǎn)化為 Class 對(duì)象

findClass() 是 JDK1.2 之后 ClassLoader 新添加的方法,在 JDK1.2 之后已不提倡用戶直接覆蓋 loadClass() 方法,而是建議把自己的類加載邏輯實(shí)現(xiàn)到 findClass() 方法中,因?yàn)樵?loadClass() 方法的邏輯里,如果父類加載器加載失敗,則會(huì)調(diào)用自己的 findClass() 方法來(lái)完成加載。而同樣如果你想定義一個(gè)自己的類加載器,并且要遵守雙親委派模型,那么可以繼承 ClassLoader,并且在 findClass() 中實(shí)現(xiàn)你自己的加載邏輯即可

對(duì)于雙親委派模型的破壞,最經(jīng)典例子就是 Tomcat 容器的類加載機(jī)制了,它實(shí)現(xiàn)了自己的類加載器 WebApp ClassLoader,并且打破了雙親委派模型,在每個(gè)應(yīng)用在部署后,都會(huì)創(chuàng)建一個(gè)唯一的類加載器,對(duì)此感興趣的讀者可以閱讀后面這篇文章:Tomcat 的類加載機(jī)制

總結(jié)

到此這篇關(guān)于Java虛擬機(jī)之對(duì)象創(chuàng)建過(guò)程與類加載機(jī)制及雙親委派模型的文章就介紹到這了,更多相關(guān)Java虛擬機(jī)對(duì)象創(chuàng)建過(guò)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何利用 Either 和 Option 進(jìn)行函數(shù)式錯(cuò)誤處理

    如何利用 Either 和 Option 進(jìn)行函數(shù)式錯(cuò)誤處理

    這篇文章主要介紹了如何利用 Either 和 Option 進(jìn)行函數(shù)式錯(cuò)誤處理。在 Java 中,錯(cuò)誤的處理在傳統(tǒng)上由異常以及創(chuàng)建和傳播異常的語(yǔ)言支持進(jìn)行。但是,如果不存在結(jié)構(gòu)化異常處理又如何呢?,需要的朋友可以參考下
    2019-06-06
  • SpringBoot thymeleaf的使用方法解析

    SpringBoot thymeleaf的使用方法解析

    這篇文章主要介紹了SpringBoot thymeleaf的使用方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • 詳解jvm雙親委派機(jī)制

    詳解jvm雙親委派機(jī)制

    雙親委派機(jī)制保證了核心類的安全,確保不會(huì)被修改,也保證了不會(huì)加載到重復(fù)的字節(jié)碼文件,這篇文章主要介紹了jvm雙親委派機(jī)制詳解,需要的朋友可以參考下
    2022-11-11
  • spring boot攔截器的使用場(chǎng)景示例詳解

    spring boot攔截器的使用場(chǎng)景示例詳解

    這篇文章主要給大家介紹了關(guān)于spring boot攔截器的使用場(chǎng)景,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • Jmeter跨線程組共享cookie過(guò)程圖解

    Jmeter跨線程組共享cookie過(guò)程圖解

    這篇文章主要介紹了Jmeter跨線程組共享cookie過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java中跳出多重循環(huán)嵌套的三種方法

    Java中跳出多重循環(huán)嵌套的三種方法

    這篇文章主要給大家介紹了關(guān)于Java中跳出多重循環(huán)嵌套的三種方法,很多復(fù)雜的運(yùn)算以及邏輯可能用到嵌套循環(huán),但是如何跳出當(dāng)前的多重嵌套循環(huán),很多同學(xué)特別是新手都操作的不是很清楚,需要的朋友可以參考下
    2023-07-07
  • Java通過(guò) Socket 實(shí)現(xiàn) TCP服務(wù)端

    Java通過(guò) Socket 實(shí)現(xiàn) TCP服務(wù)端

    這篇文章主要介紹了Java通過(guò) Socket 實(shí)現(xiàn) TCP服務(wù)端的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • springboot項(xiàng)目啟動(dòng)后執(zhí)行方法的三種方式

    springboot項(xiàng)目啟動(dòng)后執(zhí)行方法的三種方式

    有時(shí)項(xiàng)目需求,需要項(xiàng)目啟動(dòng)的時(shí)候向數(shù)據(jù)庫(kù)中查詢一下系統(tǒng)屬性,或者需要加載某個(gè)特定的方法,下面這篇文章主要給大家介紹了關(guān)于springboot項(xiàng)目啟動(dòng)后執(zhí)行方法的三種方式,需要的朋友可以參考下
    2022-06-06
  • Java棧的三種實(shí)現(xiàn)方式(完整版)

    Java棧的三種實(shí)現(xiàn)方式(完整版)

    這篇文章主要介紹了Java棧的三種實(shí)現(xiàn)方式(完整版),需要的朋友可以參考下
    2020-12-12
  • 淺談System.getenv()和System.getProperty()的區(qū)別

    淺談System.getenv()和System.getProperty()的區(qū)別

    這篇文章主要介紹了System.getenv()和System.getProperty()的區(qū)別,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06

最新評(píng)論