柚子快報(bào)激活碼778899分享:開發(fā)語言 Java類的加載機(jī)制
柚子快報(bào)激活碼778899分享:開發(fā)語言 Java類的加載機(jī)制
一、類的生命周期
1、加載的生命周期
類從被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Intialization)、使用(Using)、卸載(Unloding)7個(gè)階段。其中驗(yàn)證、準(zhǔn)備、解析 3個(gè)部分統(tǒng)稱為鏈接(Linking)。
加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這5個(gè)階段的順序是確定的,類的加載過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開始,這是為了支持Java語言的運(yùn)行時(shí)綁定(也稱為動(dòng)態(tài)綁定或晚期綁定)。
加載階段,Java虛擬機(jī)規(guī)范中沒有進(jìn)行約束。但是初始化階段,Java虛擬機(jī)規(guī)范中有嚴(yán)格的約束。(注:加載、驗(yàn)證、準(zhǔn)備要在初始化之前開始)
下面5種情況必須立即對(duì)類進(jìn)行初始化:
1)遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。生成這4條指令的最常見的Java代碼場(chǎng)景是:使用new 關(guān)鍵字實(shí)例化對(duì)象的時(shí)候、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。
2)使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
3)當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。
4)當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
5)當(dāng)使用JDK 1.7的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
注意:對(duì)于這5種會(huì)觸發(fā)類進(jìn)行初始化的場(chǎng)景,虛擬機(jī)規(guī)范中使用了一個(gè)強(qiáng)烈限定語:”有且只有“,這5種場(chǎng)景中的行為稱為對(duì)一個(gè)類進(jìn)行主動(dòng)引用。除此之外,所有引用類的方式都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用。
2.被動(dòng)使用類字段舉例
(1)通過子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化。
定義一個(gè)類SuperClass:
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
public static int value = 123;
}
定義SuperClass的一個(gè)子類:
public class ChildClass extends SuperClass {
static {
System.out.println("ChildClass init!");
}
public static int childvalue = 456;
}
測(cè)試1——通過子類獲取父類的屬性:
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ChildClass.value);
}
}
上述代碼運(yùn)行之后,只會(huì)輸出”SuperClass init!“:
?測(cè)試1——通過子類獲取自己的屬性:
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ChildClass.childvalue);
}
}
打印結(jié)果:
對(duì)于靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此通過其子類來引用父類中定義的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化而不會(huì)觸發(fā)子類的初始化。
(2)通過數(shù)組定義來引用類,不會(huì)觸發(fā)此類的初始化
例如:
public class NotInitialization {
public static void main(String[] args) {
SuperClass[] sca=new SuperClass[10];
}
}
運(yùn)行之后發(fā)現(xiàn)沒有輸出”SuperClass init!“,說明并沒有觸發(fā)類com.example.mytestapplication.test.load.SuperClass的初始化階段。但是這段代碼里面觸發(fā)了另外一個(gè)名為”[Lcom.example.mytestapplication.test.load.SuperClass“的類的初始化階段,它是由虛擬機(jī)自動(dòng)生成的、直接繼承于java.lang.Object的子類,創(chuàng)建動(dòng)作由字節(jié)碼指令newarray出發(fā)。
這個(gè)類代表了com.example.mytestapplication.test.load.SuperClass的一維數(shù)組,數(shù)組中應(yīng)由的屬性和方法(用戶可直接使用的只有被修飾為public的length屬性和clone()方法)都實(shí)現(xiàn)在這個(gè)類里。
(3)常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上并沒有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化。
例如:
public class ConstClass {
static{
System.out.println("ConstClass init!");
}
public static final String HELLOWORLD="hello world";
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWORLD);
}
}
上述代碼運(yùn)行之后,也沒有輸出”ConstClass init!“,這是因?yàn)殡m然在Java源碼中引用了ConstClass類中的常量HELLOWORLD,但其實(shí)在編譯階段通過常量傳播優(yōu)化,已經(jīng)將此常量的值”hello world“存儲(chǔ)到了NotInitialization類的常量池中,以后NotInitialization對(duì)常量ConstClass.HELLOWORLD的引用實(shí)際都被轉(zhuǎn)化為NotInitialization類對(duì)自身常量池的引用了。也就是說,實(shí)際上NotInitialization的Class文件之中并沒有ConstClass類的符號(hào)引用入口,這兩個(gè)類在編譯成Class之后就不存在任何聯(lián)系了。
3.其他不會(huì)觸發(fā)初始化的情況
(1)通過類名獲取Class對(duì)象,不會(huì)觸發(fā)類的初始化。如System.out.println(User.class);
(2)通過Class.forName加載指定類時(shí),如果指定參數(shù)initialize為false時(shí),也不會(huì)觸發(fā)類初始化。
Class.forName有兩個(gè)重載的方法:
其中只有一個(gè)參數(shù)的Class.forName方法默認(rèn)initialize為true,即會(huì)觸發(fā)類的初始化:
public static Class> forName(String className)
throws ClassNotFoundException {
Class> caller = Reflection.getCallerClass();
return forName(className, true, ClassLoader.getClassLoader(caller));
}
有三個(gè)參數(shù)的Class.forName方法,initialize參數(shù)可以由外部傳入:
public static Class> forName(String name, boolean initialize,
ClassLoader loader)
(3)通過ClassLoader默認(rèn)的loadClass方法,也不會(huì)觸發(fā)初始化動(dòng)作。
4、接口的加載
接口的加載過程與類的加載過程稍有不同,針對(duì)接口需要做一些特殊說明:接口也有初始化過程,這點(diǎn)與類是一致的,上面的代碼都是用靜態(tài)語句塊”static{}“來輸出初始化信息的,而接口中不能使用”static{}“語句塊,但編譯器仍然會(huì)為接口生成”
二、階段詳細(xì)分析
1、加載階段
加載主要是將.class文件通過二進(jìn)制字節(jié)流讀入到JVM中。加載階段,虛擬機(jī)需要完成以下三件事情:
(1)通過classloader在classpath中讀取xxx.class文件,將其以二進(jìn)制流的形式讀入內(nèi)存
(2)將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
(3)在內(nèi)存中生成一個(gè)該類的java.lang.Class對(duì)象,這樣便可以通過該對(duì)象訪問方法區(qū)中的這些數(shù)據(jù)
class文件的加載方式:
從本地系統(tǒng)重直接加載從zip、jar等歸檔文件中加載.class文件從數(shù)據(jù)庫中提取.class文件將Java源文件動(dòng)態(tài)編譯為.class文件
相對(duì)于類的生命周期的其他階段而言,加載階段(準(zhǔn)確地說,是加載階段獲取類的二進(jìn)制字節(jié)流的動(dòng)作)是可控性最強(qiáng)的階段,因?yàn)殚_發(fā)人員既可以使用系統(tǒng)提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。
2、鏈接階段
當(dāng)類被加載后,系統(tǒng)為之生成一個(gè)對(duì)應(yīng)的Class對(duì)象,接著會(huì)進(jìn)入鏈接階段。類的鏈接階段分為如下三個(gè)階段:
驗(yàn)證:驗(yàn)證階段用于檢驗(yàn)被加載的類是否有正確的內(nèi)部結(jié)構(gòu),并和其他類協(xié)調(diào)一致;準(zhǔn)備:準(zhǔn)備階段則負(fù)責(zé)為類的靜態(tài)屬性分配內(nèi)存,并設(shè)置默認(rèn)初始值解析:將類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換成直接引用(符號(hào)引用是用一組符號(hào)描述所引用的目標(biāo);直接引用是指向目標(biāo)的指針)
(1)驗(yàn)證
確保被加載的類的正確性
驗(yàn)證階段主要是確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)的自身安全。
包括以下幾個(gè)方面的驗(yàn)證:
文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,如:是否以魔數(shù)0xCAFEBABE開頭、主次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi)、常量池的常量中是否有不被支持的常量類型(檢查常量tag標(biāo)志)、指向常量的各種索引值中是否有指向不存在的常量或不符合類型的常量等等
元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼的描述的信息進(jìn)行了語義分析,以保證其描述的信息符合Java語言規(guī)范的要求;如:這個(gè)類是否有父類,是否實(shí)現(xiàn)了父類的抽象方法,是否重寫了父類的final方法,是否繼承了被final修飾的類等等。
字節(jié)碼驗(yàn)證:通過數(shù)據(jù)流和控制流分析,確定程序語義是否是合法的、符合邏輯的,如:操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列能配合工作,保證方法中的類型轉(zhuǎn)換有效等待。
符號(hào)引用驗(yàn)證:確保解析動(dòng)作能正確執(zhí)行;如:符號(hào)引用中通過字符串描述的全限定名是否能找到對(duì)應(yīng)的類、在指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段、符號(hào)引用中的類、字段、方法的訪問性(private、protected、public、default)是否可被當(dāng)前類訪問。
符號(hào)引用驗(yàn)證的目的是確保解析動(dòng)作能正常執(zhí)行,如果無法通過符號(hào)引用驗(yàn)證,那么將會(huì)拋出一個(gè)java.lang.IncompatibleClassChangeError異常的子類,如java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。
對(duì)于虛擬機(jī)的類加載機(jī)制來說,驗(yàn)證階段是一個(gè)非常重要的、但不是一定必要(因?yàn)閷?duì)程序運(yùn)行期沒有影響)的階段。如果所運(yùn)行的全部代碼(包括自己編寫的及第三方包中的代碼)都已經(jīng)被反復(fù)使用和驗(yàn)證過,那么在實(shí)施階段就可以考慮使用-Xverify:none參數(shù)來關(guān)閉大部分的類驗(yàn)證措施,以縮短虛擬機(jī)類加載的時(shí)間。
(2)準(zhǔn)備
為類的靜態(tài)變量分配內(nèi)存,并將其賦默認(rèn)值
準(zhǔn)備階段是正式為類變量(被static修飾的變量)分配內(nèi)存并設(shè)置類初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。
注:這時(shí)候進(jìn)行內(nèi)存分配的僅僅包括類變量(被static修飾的變量),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中。其次,這里所說的初始值”通常情況“下是數(shù)據(jù)類型的零值,假設(shè)一個(gè)類變量的定義為:
public static int value = 123;
那么變量value在準(zhǔn)備階段過后的初始值是0而不是123,因?yàn)檫@時(shí)候尚未開始執(zhí)行任何Java方法,而把value賦值為123的putstatic指令是程序被編譯后,存放于類構(gòu)造器
上面提到,在”通常情況“下初始值是零值,那相對(duì)的會(huì)有一些”特殊情況“:如果類字段的字段屬性表中存在ConstantValue屬性,那么在準(zhǔn)備階段變量value就會(huì)被初始化為ConstantValue屬性所指定的值,假設(shè)上面類變量value的定義變?yōu)椋?/p>
public static final int value=123;
編譯時(shí)Javac將會(huì)為value生成ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue的設(shè)置將value賦值為123。
小結(jié):
對(duì)static修飾的靜態(tài)變量進(jìn)行內(nèi)存分配、并賦默認(rèn)值對(duì)final static 修飾的靜態(tài)字面值常量進(jìn)行內(nèi)存分配、直接賦初始值。對(duì)final static修飾的靜態(tài)非字面值常量進(jìn)行內(nèi)存分配、賦默認(rèn)值
一句話:靜態(tài)變量和靜態(tài)非字面值常量賦默認(rèn)值,靜態(tài)字面值常量賦初始值
字面值常量和非字面值常量
public static final long id=IdGenerator.getIdWorker().nextId();//靜態(tài)非字面值常量,需要初始化ClassInit類
public static final String str="abc";//靜態(tài)字面值常量
(3)解析
將常量池中符號(hào)引用替換為直接引用(內(nèi)存地址)的過程
符號(hào)引用:一組符號(hào)來描述目標(biāo),可以是任何字面量。
直接引用:直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄
假設(shè)一個(gè)類有一個(gè)靜態(tài)變量,該靜態(tài)變量是一個(gè)自定義的類型,那么經(jīng)過解析后,該變量將是一個(gè)指針,指向該類在方法區(qū)的內(nèi)存地址。
解析動(dòng)作主要針對(duì)類或接口、字段、類方法、接口方法、方法類型、方法句柄、調(diào)用點(diǎn)限定符這7類符號(hào)引用進(jìn)行。
這7類符號(hào)引用解析分別對(duì)應(yīng)于常量池的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info、CONSTANT_InvokeDynamic_info 7中常量類型。
(4)初始化
為類的靜態(tài)變量賦初始值
靜態(tài)變量賦初始值的兩種方式:
定義靜態(tài)變量時(shí)指定初始值
private static String x="123";
靜態(tài)代碼塊里為靜態(tài)變量賦值
private static String x;
static{
x="123";
}
在編譯生成class文件時(shí),編譯器會(huì)產(chǎn)生兩個(gè)方法加于class文件中:
clinit:類的初始化方法init:實(shí)例的初始化方法
初始化階段是執(zhí)行類構(gòu)造器
?
示例:
public class Parent {
public static int A=1;
static{
A=2;
}
}
public class Sub extends Parent{
public static int B=A;
}
public class Test {
public static void main(String[] args) {
System.out.println(Sub.B);
}
}
如在上述代碼中,打印字段B的結(jié)果是2而不是1。
接口中不能使用靜態(tài)代碼塊,但仍然有變量初始化的賦值操作,因此接口與類一樣都會(huì)生成
虛擬機(jī)會(huì)保證一個(gè)類的
小結(jié)
1.
clinit是類的構(gòu)造器,主要作用是在類加載的過程中的初始化階段進(jìn)行執(zhí)行,執(zhí)行內(nèi)容包括:靜態(tài)變量初始化(賦初始值)和靜態(tài)代碼塊的執(zhí)行
如果類中沒有靜態(tài)變量或靜態(tài)代碼塊,那么clinit方法將不會(huì)被生成類在執(zhí)行clinit方法時(shí),必須先執(zhí)行父類的clinit方法。而接口在執(zhí)行clinit方法時(shí),不會(huì)執(zhí)行父接口的clinit方法。clinit方法只執(zhí)行一次靜態(tài)變量的賦初始值和靜態(tài)代碼塊的執(zhí)行順序由源文件中出現(xiàn)的順序決定
2、
init是實(shí)例的構(gòu)造器,主要作用是在類實(shí)例化的過程中執(zhí)行,執(zhí)行內(nèi)容包括:成員變量初始化和構(gòu)造代碼塊的執(zhí)行。
如果類中沒有成員變量和構(gòu)造代碼塊,那么init方法將不會(huì)被生成在執(zhí)行init方法時(shí),必須先執(zhí)行父類的init方法init方法每實(shí)例化一次就會(huì)執(zhí)行一次init方法先為實(shí)例變量分配內(nèi)存空間,再執(zhí)行默認(rèn)值,然后進(jìn)行賦初值和構(gòu)造代碼塊的執(zhí)行(實(shí)例變量的賦初值和構(gòu)造代碼塊的執(zhí)行順序由源文件中出現(xiàn)的順序決定)
三、類加載器
虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的”通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流“這個(gè)動(dòng)作放到Java虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為”類加載器“。
對(duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間。通俗地說:比較兩個(gè)類是否”相等“,只有在這兩個(gè)類是同一個(gè)類加載器加載的前提下才有意義,否則,即使這兩個(gè)類來源于同一個(gè)Class文件,被同一個(gè)虛擬機(jī)加載,只要加載它們的類加載器不同,那這兩個(gè)類就必定不相等。
這里所說的”相等“,包括代表類的Class對(duì)象的equals()方法、isAsignableFrom()方法、isInstance()方法的返回結(jié)果,也包括使用instanceof關(guān)鍵字做對(duì)象所屬關(guān)系判定等情況。
1、雙親委派模型
從Java虛擬機(jī)的角度來講,只存在兩種不同的類加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器使用C++語言實(shí)現(xiàn),是虛擬機(jī)自身的一部分;另一種就是所有其他的類加載器,這些類加載器都由Java語言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類java.lang.ClassLoader。
從Java開發(fā)人員的角度看,類加載器可以劃分為:啟動(dòng)類加載器(BootstrapClassLoader)、擴(kuò)展類加載器(ExtensionClassLoader)、應(yīng)用程序類加載器(AppClassLoader)、自定義加載器(CustomClassLoader)。
(1)啟動(dòng)類加載器
負(fù)責(zé)加載$JAVA_HOME jre/lib/rt.jar里所有的class或者-Xbootclasspath參數(shù)所指定的路徑中,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會(huì)被加載)。由C++識(shí)別,不是ClassLoader子類
(2)擴(kuò)展類加載器
負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar? 或者 -Djava.ext.dirs指定目錄下的jar包
(3)應(yīng)用程序類加載器
負(fù)責(zé)加載classpath中指定的jar包或者 -Djava.class.path所指定目錄下的類和jar包。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也稱它為系統(tǒng)類加載器。它負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫,開發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器
(4)自定義加載器
通過java.lang.ClassLoader的子類自定義加載class,屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader。如tomcat、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader。
?雙親委派模型(Parents Delegation Model)要求除了頂層的啟動(dòng)類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里類加載器之間的父子關(guān)系一般不會(huì)以繼承的關(guān)系來實(shí)現(xiàn),而是都使用組合關(guān)系來復(fù)用父加載器的代碼。
雙親委派模型的工作過程:
如果一個(gè)類加載器收到了類的加載請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒有找到所需的類)時(shí),子類加載器才會(huì)嘗試自己去加載。
雙親委派模型的好處:
避免類的重復(fù)加載:在JVM中,每個(gè)類都由一個(gè)唯一的全限定名和一個(gè)對(duì)應(yīng)的類加載器確定,類加載器根據(jù)全限定名和類路徑來確定類的位置。因此一個(gè)JVM實(shí)例中,如果有兩個(gè)類加載器分別加載了同一個(gè)類,JVM會(huì)認(rèn)為這兩個(gè)類是不同的,從而導(dǎo)致類型轉(zhuǎn)換異常等問題。通過雙親委派機(jī)制,類加載器在加載類之前會(huì)先委托給自己的父類加載器去加載,從而保證一個(gè)類在JVM中只會(huì)有一份,并且由其父類加載器所加載。保護(hù)程序安全,防止核心API被隨意篡改:Java核心類庫(java.lang 包下的類)都是由啟動(dòng)類加載器加載的,其他的類都是由其他類加載器加載的。這樣,我們可以保證Java核心類庫的安全性,因?yàn)椴煌膽?yīng)用程序無法改變這些類的實(shí)現(xiàn)。
Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如類java.lang.Object,它存放在rt.jar之中,無論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。相反,如果沒有使用雙親委派模型,由各個(gè)類加載器自行去加載的話,如果用戶自己編寫了一個(gè)稱為java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,Java類型體系中最基本的行為也就無法保證,應(yīng)用程序也將會(huì)變得一片混亂。
雙親委派模型對(duì)于保證Java程序的穩(wěn)定運(yùn)作很重要,但它的實(shí)現(xiàn)卻非常簡單,實(shí)現(xiàn)雙親委派的代碼都集中在java.lang.ClassLoader的loadClass()方法之中。邏輯清晰易懂:先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父加載器的loadClass()方法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父類加載失敗,拋出ClassNotFoundException異常后,再調(diào)用自己的findClass()方法進(jìn)行加載。
protected synchronized Class> loadClass(String name, boolean resolve) throws
ClassNotFoundException
{
//首先,檢查請(qǐng)求的類是否已經(jīng)被加載過了
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//如果父類加載器拋出ClassNotFoundException
//說明父類加載器無法完成加載請(qǐng)求
}
if (c == null) {
//在父類加載器無法加載的時(shí)候
//再調(diào)用本身的findClass方法來進(jìn)行類加載
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
四、問題
1、為什么Java中靜態(tài)方法不能調(diào)用非靜態(tài)方法和變量?
因?yàn)檎{(diào)用靜態(tài)方法時(shí),是沒有傳入this指針的,所以在靜態(tài)方法中調(diào)用非靜態(tài)方法,非靜態(tài)方法的第一個(gè)參數(shù)是隱含的,靜態(tài)方法沒有這個(gè)隱含參數(shù),所以不能調(diào)用。
例如: ?
class Hello{
String name;
void hello(){
System.out.println('hello'+name);
}
static void greeting(){
System.out.println('hello'+name);
}
}
?在編譯器看來,上面的代碼就是數(shù)據(jù)結(jié)構(gòu)+函數(shù)調(diào)用:
class Hello {
String name;
}
void Hello::hello(Hello this) {
System.out.println('hello, ' + this.name);
}
void Hello::greeting
非靜態(tài)方法第一個(gè)隱含參數(shù) Hello this是編譯器自動(dòng)加上的,靜態(tài)方法沒有這個(gè)隱含參數(shù),所以hello()可以編譯通過,但是greeting()不行,因?yàn)閚ame undefined。
柚子快報(bào)激活碼778899分享:開發(fā)語言 Java類的加載機(jī)制
文章鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。