java class文件的加載

轉(zhuǎn)自 http://blog.csdn.net/wen7280/article/details/53856790

圖覽全局----Class文件裝載經(jīng)歷的各個階段:

Java應用程序開發(fā)中挠轴,只有被java虛擬機裝載的Class類型才能在程序中使用践瓷。只要生成的字節(jié)碼符合java虛擬機的指令集和文件格式蓖租,就可以在JVM上運行劫瞳,這為java的跨平臺性提供條件。

字節(jié)碼文件的裝載過程:加載 饶碘、 ?連接(包括三個步驟:驗證 ?準備 ? 解析) ?、初始化,如圖所示

-------------------------------------------------------------------------------------------------

類裝載的條件:

Java虛擬機不會無條件的裝載Class類型表悬。

Java虛擬機規(guī)定:一個類或者接口在初次使用時,必須進行初始化丧靡。

這里的使用指的是主動使用蟆沫,主動使用有以下幾種情況:

當創(chuàng)建一個類的實例時,比如使用new關鍵字温治,或者通過反射饭庞、克隆、反序列化方式熬荆。

當調(diào)用類的靜態(tài)方法時舟山,即當使用了字節(jié)碼invokestatic指令

當使用類或者接口的靜態(tài)字段時(final常量除外,此種情況只會加載類而不會進行初始化)卤恳,即使用getstatic或者putstatic指令(可以使用jclasslib軟件查看生成的字節(jié)碼文件)

當使用java.lang.reflect包中的方法反射類的方法時

當初始化子類時累盗,必須先初始化父類

作為啟動虛擬機、含有main方法的那個類

除了以上情況屬于主動使用外突琳,其他情況均屬于被動使用若债,被動使用不會引起類的初始化,只是加載了類卻沒有初始化拆融。

例1:主動使用(這是三個class文件蠢琳,而不是一個,此處為方便寫在一起冠息。多說一點:因為一個Class文件只能有一個public類和文件名一樣挪凑,其余類修飾符只能是非pubic)

[java]view plaincopy

publicclassParent{

static{

System.out.println("Parent?init");

}

}

publicclassChild{

static{

System.out.println("Child?init");

}

}

publicclassInitMain{

publicstaticvoidmain(String[]?args){

Child?c?=newChild();

}

}

以上聲明了3個類:Parent Child InitMain,Child類為Parent類的子類逛艰。若Parent被初始化躏碳,將會執(zhí)行static塊,會打印"Parent init"散怖,若Child被初始化菇绵,則會打印"Child init"。(類的初始化先于加載镇眷,故執(zhí)行靜態(tài)代碼塊后咬最,就表明類已經(jīng)加載了)

[java]view plaincopy

執(zhí)行InitMain,結果為:

Parent?init

Child?init

由此可知欠动,系統(tǒng)首先裝載Parent類永乌,接著裝載Child類惑申。

符合主動裝載中的兩個條件:使用new關鍵字創(chuàng)建類的實例會裝載相關的類,以及在初始化子類時翅雏,必須先初始化父類圈驼。

例2 :被動裝載

[java]view plaincopy

publicclassParent{

static{

System.out.println("Parent?init?");

}

publicstaticintv?=100;//靜態(tài)字段

}

publicclassChildextendsParent{

static{

System.out.println("Child?init");

}

}

publicclassUserParent{

publicstaticvoidmain(String[]?args){

System.out.println(Child.v);

}

}

Parent中有靜態(tài)變量v,并且在UserParent中望几,使用其子類Child去調(diào)用父類中的變量绩脆。

[java]view plaincopy

運行代碼:

Parent?init

100

雖然在UserParent中,直接訪問了子類對象橄抹,但是Child子類并未初始化靴迫,僅僅加載了Child類,只有Parent類進行初始化楼誓。所以玉锌,在引用一個字段時,只有直接定義該字段的類慌随,才會被初始化芬沉。

注意:雖然Child類沒有被初始化,但是阁猜,此時Child類已經(jīng)被系統(tǒng)加載丸逸,只是沒有進入初始化階段。

可以使用-XX:+ThraceClassLoading 參數(shù)運行這段代碼剃袍,查看日志黄刚,便可以看到Child類確實被加載了,只是初始化沒有進行

例3 :引用final常量

[java]view plaincopy

publicclassFinalFieldClass{

publicstaticfinalString?constString?="CONST";

static{

System.out.println("FinalFieldClass?init");

}

}

publicclassUseFinalField{

publicstaticvoidmain(String[]?args){

System.out.println(FinalFieldClass.constString);

}

}

運行代碼:CONST

FinalFieldClass類沒有因為其常量字段constString被引用而進行初始化民效,這是因為在Class文件生成時憔维,final常量由于其不變性,做了適當?shù)膬?yōu)化畏邢。驗證完字節(jié)碼文件無誤后业扒,在準備階段就會為常量初始化為指定的值。

分析UseFinalField類生成的Class文件舒萎,可以看到main函數(shù)的字節(jié)碼為:

在字節(jié)碼偏移3的位置程储,通過Idc將常量池第22項入棧,在此Class文件中常量池第22項為:

#22 = String ? ? ? ?#23 ? ? //CONST

#23 = UTF8 ? ? ? ? CONST

由此可以看出臂寝,編譯后的UseFinalField.class中章鲤,并沒有引用FinalFieldClass類,而是將FinalFieldClass類中final常量字段直接存放在自己的常量池中咆贬,所以败徊,F(xiàn)inalFiledClass類自然不會被加載。(javac在編譯時掏缎,將常量直接植入目標類皱蹦,不再使用被引用類)通過捕獲類加載日志(部分日志)可以看出:(并沒有加載FinalFiledClass類日志)

注意:并不是在代碼中出現(xiàn)的類煤杀,就一定會被加載或者初始化,如果不符合主動使用的條件根欧,類就不會被加載或者進一步初始化怜珍。

詳解類裝載的整個過程

1)加載類:處于類裝載的第一個階段。

加載類時凤粗,JVM必須完成:

通過類的全名,獲取類的二進制數(shù)據(jù)流

解析類的二進制數(shù)據(jù)流為方法區(qū)內(nèi)的數(shù)據(jù)結構今豆,也就是將類文件放入方法區(qū)中

創(chuàng)建java.lang.Class類的實例嫌拣,表示該類型

2)連接

驗證字節(jié)碼文件:當類被加載到系統(tǒng)后,就開始連接操作呆躲,驗證是連接的第一步异逐。

主要目的是保證加載的字節(jié)碼是符合規(guī)范的。

驗證的步驟如圖:

準備階段

當一個類驗證通過后插掂,虛擬機就會進入準備階段灰瞻。準備階段是正式為類變量(static修飾的變量)分配內(nèi)存并設置類變量初始值,這些內(nèi)存都將在方法區(qū)進行分配辅甥。這個時候進行內(nèi)存分配的僅是類變量酝润,不包括實例變量,實例變量將會在對象實例化時隨著對象一起分配在堆上璃弄。為類變量設置初始值是設為其數(shù)據(jù)類型的“零值”要销。

比如 public static int num = 12; 這個時候就會為num變量賦值為0

java虛擬機為各種類型變量默認的初始值如表:

類型默認初始值

int0

long0L

short(short)0

char\u0000

booleanfalse

referencenull

float0f

double0f

注意:java并不支持boolean類型,對于boolean類型夏块,內(nèi)部實現(xiàn)是Int疏咐,由于int的默認值是0,故對應的脐供,boolean的默認值是false

如果類中屬于常量的字段浑塞,那么常量字段也會在準備階段被附上正確的值,這個賦值屬于java虛擬機的行為政己,屬于變量的初始化酌壕。在準備階段,不會有任何java代碼被執(zhí)行匹颤。

解析類

在準備階段完成后仅孩,就進入了解析階段。

解析階段的任務就是將類印蓖、接口辽慕、字段和方法的符號引用轉(zhuǎn)為直接引用。

符號引用就是一些字面量的引用赦肃。比較容易理解的就是在Class類文件中溅蛉,通過常量池進行大量的符號引用公浪。

具體可以使用JclassLib軟件查看Class文件的結構:::

下面通過一個簡單函數(shù)的調(diào)用來講解下符號引用是如何工作的。船侧。欠气。

例如:System.out.println();

生成的字節(jié)碼指令:invokevirtual #24

這里使用了常量池第24項,查看并分析該常量池镜撩,可以查看到如圖的結構:

常量池第24項被invokevirtual使用预柒,順著CONSTANT_Methodref #24的引用關系繼續(xù)在常量池中查找,發(fā)現(xiàn)所有對于Class以及NameAndType類型的引用都是基于字符串的袁梗,因此宜鸯,可以認為Invokevirtual的函數(shù)調(diào)用通過字面量的引用描述已經(jīng)表達清楚了,這就是符合引用遮怜。

但是只有符合引用是不夠的淋袖,當println()方法被調(diào)用時,系統(tǒng)需要明確知道方法的位置锯梁。java虛擬機會為每個類準備一張方法表即碗,將其所有的方法都列在表中,當需要調(diào)用一個類的方法時陌凳,只要知道這個方法在表中的偏移量就可以了剥懒。通過解析操作,符合引用就可以轉(zhuǎn)變?yōu)槟繕朔椒ㄔ陬愔蟹椒ū淼奈恢梅胨欤瑥亩狗椒ū怀晒φ{(diào)用蕊肥。

所以,解析的目的就是將符合引用轉(zhuǎn)變?yōu)橹苯右酶蚣。褪堑玫筋惢蛘咦侄伪谌础⒎椒ㄔ趦?nèi)存中的指針或者偏移量。如果直接引用存在裸准,那么系統(tǒng)中肯定存在類展东、方法或者字段,但只存在符合引用炒俱,不能確定系統(tǒng)中一定存在該對象盐肃。

3)類初始化

如果前面的步驟沒有出現(xiàn)問題,那么表示類可以順利裝載到系統(tǒng)中权悟。此時砸王,才會開始執(zhí)行java字節(jié)碼

初始化階段的重要工作是執(zhí)行類的初始化方法()峦阁。其特點:

()方法是由編譯器自動生成的馏段,它是由類靜態(tài)成員的賦值語句以及static語句塊合并產(chǎn)生的婶博。編譯器收集的順序是由語句在源文件中出現(xiàn)的順序決定的慷暂,靜態(tài)語句塊中只能訪問到定義在靜態(tài)語句塊之前的類變量,定義在其之后的類變量瘪菌,只能被賦值,不能被訪問嘹朗。比如:

static{

num = 5;//這是合法的

}

static int num = 12;

static{

System.out.println(num);//這樣是不合法的

}

static int num = 12;

例如:

[java]view plaincopy

publicclassSimpleStatic{

publicstaticintid?=1;

publicstaticintnumber;

static{

number?=4;

}

}

java編譯器為這段代碼生成如下的:

0 iconst_1

1 putstatic #2

4 iconst_4

5 putstatic #3

8 return

函數(shù)中师妙,整合了SimpleStatic類中的static賦值語句以及static語句塊

改段JVM指令代碼表示:先后對id和number兩個成員變量進行賦值

()方法與類的構造器函數(shù)()方法不同,它不需要顯示的調(diào)用父類的()方法屹培,虛擬機會保證在子類的()方法執(zhí)行之前默穴,父類的()方法已經(jīng)執(zhí)行完畢。故父類的靜態(tài)語句塊會先于子類的靜態(tài)語句塊執(zhí)行褪秀。

[java]view plaincopy

publicclassChildStaticextendsSimpleStatic

{

static{

number?=2;

}

publicstaticvoidmain(String[]?args){

System.out.println(number);

}

}

[java]view plaincopy

運行代碼:

2

表明父類的總是在子類之前被調(diào)用壁顶。

注意java編譯器并不是為所有的類都產(chǎn)生初始化函數(shù),如果一個類既沒有類變量賦值語句溜歪,也沒有static語句塊,那么生成的函數(shù)就應該為空许蓖,因此蝴猪,編譯器就不會為該類插入函數(shù)

例如:

public class StaticFinalClass{

public static final int i=1;

public static final int j=2;

}

由于StaticFinalClass只有final常量,而final常量在準備階段被賦值膊爪,而不在初始化階段處理自阱,因此對于StaticFinalClass類來說,就無事可做米酬,因此沛豌,在產(chǎn)生的class文件中沒有該函數(shù)存在。

虛擬機保證一個類的()方法在多線程環(huán)境中被正確的加鎖和同步赃额,如果多個線程同時去初始化一個類加派,只有一個線程去執(zhí)行這個類的()方法,其他線程都會被阻塞跳芳,直到指定線程執(zhí)行完()方法芍锦。

--------------------------------------------------------------------------------------------------------------------------------------------------

趁著意猶未盡,來看看對象初始化流程:包括成員變量和構造器調(diào)用的先后順序飞盆,子類構造器和父類之間的先后順序等等娄琉。通過字節(jié)碼文件指令直接的展示這個過程

編輯幾個類,包括一個子類一個父類吓歇,其中子類和父類中都包含了成員變量孽水、非靜態(tài)代碼塊、構造器函數(shù)以及前面講到的靜態(tài)代碼塊和靜態(tài)變量:

[java]view plaincopy

packagecom.classextends;

publicclassFuZiDemo?{

publicstaticvoidmain(String[]?args)?{

newZiClass();//測試類城看,創(chuàng)建子類對象

}

}

[java]view plaincopy

classFuClass?{

intfuOwer?=120;//成員變量一

static{

System.out.println("Fu?clinit()");//靜態(tài)代碼塊

}

staticintnum?=22;//靜態(tài)變量

{//非靜態(tài)代碼塊

fuName?="tempValue";

System.out.println(fuOwer);

intc?=23;

}

String?fuName?="dali";//成員變量二

FuClass(){//父類構造函數(shù)

System.out.println("Fu?init()");

fuOwer?=100;

}

}

[java]view plaincopy

classZiClassextendsFuClass?{

intziOwer?=82;//成員變量一

static{//靜態(tài)代碼塊

System.out.println("Zi?clinit()");

}

staticintnum?=2;//靜態(tài)變量

{//非靜態(tài)代碼塊

ziName?="tempValue";

System.out.println(ziOwer);

intc?=23;//局部變量

}

String?ziName?="urocle";//成員變量二

ZiClass(){//子類構造函數(shù)

ziOwer?=23;

System.out.println("Zi?init()");

}

}

分析:

一女气、類的加載和初始化

首先FuziDemo這個測試類要加載,然后執(zhí)行main指令時會new 子類對象析命,故要去加載子類的字節(jié)碼文件主卫,但是會發(fā)現(xiàn)子類有一個直接繼承類FuClass逃默,于是就會先去加載FuClass的字節(jié)碼文件,接著會初始化父類簇搅,執(zhí)行FuClass類的方法:執(zhí)行輸出語句以及為靜態(tài)成員賦值完域,其字節(jié)碼指令為:

0 getstatic #13

3 ldc#19

5 invokevirtual #21

8 bipush22

10 putstatic#27

13 return

完成父類的初始化工作之后,緊接著加載子類的字節(jié)碼文件并且執(zhí)行其()方法瘩将。其字節(jié)碼指令類似于父類的:

0 getstatic #13

3 ldc#19

5 invokevirtual #21//調(diào)用println()方法輸出 #19也就是 Zi clinit()

8iconst_2

9 putstatic#27//為靜態(tài)變量賦值

12 return

二吟税、子類和父類成員變量初始化,以及構造函數(shù)執(zhí)行順序

測試類main函數(shù)的字節(jié)碼指令:

0 new #16

3 invokespecial #18> ? ? ? ? //調(diào)用子類的初始化函數(shù)

6 return

下面看看子類ZiClass的()函數(shù)的字節(jié)碼指令:

0 aload_0

1 invokespecial #32>//首先會去調(diào)用父類的()函數(shù)

4 aload_0

5 bipush82

7putfield#34//為成員變量 ziOwer賦值為82

10 aload_0

11 ldc #36

13putfield#38//執(zhí)行非靜態(tài)代碼塊姿现,臨時為成員變量ziName賦值

16 getstatic #13//調(diào)用System.out輸出函數(shù)

19 aload_0

20 getfield #34//獲取成員變量 ziOwer的值

23 invokevirtual #40//打印輸出

26 bipush 23

28 istore_1

29 aload_0

30 ldc#43

32putfield#38//為成員變量ziName賦值為urocle

35 aload_0

36bipush 23//取出 23 ,意味著實例初始化過程中先初始化成員變量及執(zhí)行非靜態(tài)代碼塊肠仪,最后執(zhí)行構造

38putfield#34//為成員變量ziOwer賦值為23

41 getstatic #13

44 ldc #45

46 invokevirtual #21

49 return

同樣FuClass類的實例初始化函數(shù)()如下,此處不再解釋:

0 aload_0

1 invokespecial #32>

4 aload_0

5 bipush 120

7 putfield #34

10 aload_0

11 ldc #36

13 putfield #38

16 getstatic #13

19 aload_0

20 getfield #34

23 invokevirtual #40

26 bipush 23

28 istore_1

29 aload_0

30 ldc #43

32 putfield #38

35 getstatic #13

38 ldc #45

40 invokevirtual #21

43 aload_0

44 bipush 100

46 putfield #34

49 return

三 ?給出程序執(zhí)行的結果

Fu clinit()

Zi clinit() ? ? ? ?//靜態(tài)代碼塊輸出

120 ? ? ? ? ? ? ? ? //非靜態(tài)代碼塊輸出

Fu init() ? ? ? ? //構造函數(shù)輸出

82

Zi init()

總結:

(1)父類加載初始化先于子類备典,父類的優(yōu)先于子類的函數(shù)執(zhí)行

(2)如果創(chuàng)建一個子類對象异旧,父類構造函數(shù)調(diào)用先于子類構造器函數(shù)調(diào)用。在執(zhí)行構造器函數(shù)首先會初始化類中成員變量或者執(zhí)行非靜態(tài)代碼塊(這二者執(zhí)行的先后順序依賴于在源文件中出現(xiàn)的順序)提佣,然后再調(diào)用構造函數(shù)吮蛹。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拌屏,隨后出現(xiàn)的幾起案子潮针,更是在濱河造成了極大的恐慌,老刑警劉巖倚喂,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件每篷,死亡現(xiàn)場離奇詭異,居然都是意外死亡端圈,警方通過查閱死者的電腦和手機焦读,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枫笛,“玉大人吨灭,你說我怎么就攤上這事⌒糖桑” “怎么了喧兄?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長啊楚。 經(jīng)常有香客問我吠冤,道長,這世上最難降的妖魔是什么恭理? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任拯辙,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘涯保。我一直安慰自己诉濒,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布夕春。 她就那樣靜靜地躺著未荒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪及志。 梳的紋絲不亂的頭發(fā)上片排,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音速侈,去河邊找鬼率寡。 笑死,一個胖子當著我的面吹牛倚搬,可吹牛的內(nèi)容都是我干的冶共。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼每界,長吁一口氣:“原來是場噩夢啊……” “哼比默!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盆犁,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎篡九,沒想到半個月后谐岁,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡榛臼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年伊佃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沛善。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡航揉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出金刁,到底是詐尸還是另有隱情帅涂,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布尤蛮,位于F島的核電站媳友,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏产捞。R本人自食惡果不足惜醇锚,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望坯临。 院中可真熱鬧焊唬,春花似錦恋昼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至芳杏,卻和暖如春矩屁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爵赵。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工吝秕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人空幻。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓烁峭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秕铛。 傳聞我的和親對象是個殘疾皇子约郁,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法但两,內(nèi)部類的語法鬓梅,繼承相關的語法,異常的語法谨湘,線程的語...
    子非魚_t_閱讀 31,597評論 18 399
  • (一)Java部分 1绽快、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,079評論 0 62
  • Win7下如何打開DOS控制臺? a:開始--所有程序--附件--命令提示符 b:開始--搜索程序和文件--cmd...
    逍遙嘆6閱讀 1,589評論 4 12
  • ? 傍晚,我在等熟悉的1路車擅耽,準備拉拉筋骨活孩,發(fā)現(xiàn)有個正在等車的孩子很眼熟,上前問...
    貓貓家的三角梅閱讀 1,002評論 0 0
  • 2015年最后一個月乖仇,今天很冷憾儒,再加上下雨,心情很糟糕乃沙,上課沒什么激情航夺。 這節(jié)課以往都是很無聊的,沒幾個人聽崔涂,但...
    loveofmylife閱讀 252評論 0 0