Java程序運(yùn)行時(shí),必須經(jīng)過(guò)編譯和運(yùn)行兩個(gè)步驟。首先將后綴名為.java的源文件進(jìn)行編譯,最終生成后綴名為.class的字節(jié)碼文件。然后Java虛擬機(jī)將編譯好的字節(jié)碼文件加載到內(nèi)存(這個(gè)過(guò)程被稱為類加載,是由加載器完成的),然后虛擬機(jī)針對(duì)加載到內(nèi)存的java類進(jìn)行解釋執(zhí)行,顯示結(jié)果。
Java的運(yùn)行原理
在Java中引入了虛擬機(jī)的概念,即在機(jī)器和編譯程序之間加入了一層抽象的虛擬的機(jī)器。這臺(tái)虛擬的機(jī)器在任何平臺(tái)上都提供給編譯程序一個(gè)的共同的接口。編譯程序只需要面向虛擬機(jī),生成虛擬機(jī)能夠理解的代碼,然后由解釋器來(lái)將虛擬機(jī)代碼轉(zhuǎn)換為特定系統(tǒng)的機(jī)器碼執(zhí)行。在Java中,這種供虛擬機(jī)理解的代碼叫做字節(jié)碼(ByteCode),它不面向任何特定的處理器,只面向虛擬機(jī)。每一種平臺(tái)的解釋器是不同的,但是實(shí)現(xiàn)的虛擬機(jī)是相同的。Java源程序經(jīng)過(guò)編譯器編譯后變成字節(jié)碼,字節(jié)碼由虛擬機(jī)解釋執(zhí)行,虛擬機(jī)將每一條要執(zhí)行的字節(jié)碼送給解釋器,解釋器將其翻譯成特定機(jī)器上的機(jī)器碼,然后在特定的機(jī)器上運(yùn)行。
Java代碼編譯和執(zhí)行的整個(gè)過(guò)程
Java代碼編譯是由Java源碼編譯器來(lái)完成,流程圖如下所示:
Java字節(jié)碼的執(zhí)行是由JVM執(zhí)行引擎來(lái)完成,流程圖如下所示:
Java代碼編譯和執(zhí)行的整個(gè)過(guò)程包含了以下三個(gè)重要的機(jī)制:
Java源碼編譯機(jī)制
類加載機(jī)制
類執(zhí)行機(jī)制
Java源碼編譯機(jī)制
Java 源碼編譯由以下三個(gè)過(guò)程組成:(javac –verbose 輸出有關(guān)編譯器正在執(zhí)行的操作的消息)
分析和輸入到符號(hào)表
注解處理
語(yǔ)義分析和生成class文件
最后生成的class文件由以下部分組成:
1、結(jié)構(gòu)信息。包括class文件格式版本號(hào)及各部分的數(shù)量與大小的信息
2、元數(shù)據(jù)。對(duì)應(yīng)于Java源碼中聲明與常量的信息。包含類/繼承的超類/實(shí)現(xiàn)的接口的聲明信息、域與方法聲明信息和常量池
3、方法信息。對(duì)應(yīng)Java源碼中語(yǔ)句和表達(dá)式對(duì)應(yīng)的信息。包含字節(jié)碼、異常處理器表、求值棧與局部變量區(qū)大小、求值棧的類型記錄、調(diào)試符號(hào)信息
類加載機(jī)制
JVM的類加載是通過(guò)ClassLoader及其子類來(lái)完成的,類的層次關(guān)系和加載順序可以由下圖來(lái)描述:
1)Bootstrap ClassLoader /啟動(dòng)類加載器
$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實(shí)現(xiàn),不是ClassLoader子類
2)Extension ClassLoader/擴(kuò)展類加載器
負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
3)App ClassLoader/ 系統(tǒng)類加載器
負(fù)責(zé)記載classpath中指定的jar包及目錄中class
4)Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)
屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader
加載過(guò)程中會(huì)先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個(gè)classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來(lái)逐層嘗試加載此類。
類加載雙親委派機(jī)制介紹和分析
在這里,需要著重說(shuō)明的是,JVM在加載類時(shí)默認(rèn)采用的是雙親委派機(jī)制。通俗的講,就是某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無(wú)法完成此加載任務(wù)時(shí),才自己去加載。
類執(zhí)行機(jī)制
JVM是基于棧的體系結(jié)構(gòu)來(lái)執(zhí)行class字節(jié)碼的。線程創(chuàng)建后,都會(huì)產(chǎn)生程序計(jì)數(shù)器(PC)和棧(Stack),程序計(jì)數(shù)器存放下一條要執(zhí)行的指令在方法內(nèi)的偏移量,棧中存放一個(gè)個(gè)棧幀,每個(gè)棧幀對(duì)應(yīng)著每個(gè)方法的每次調(diào)用,而棧幀又是有局部變量區(qū)和操作數(shù)棧兩部分組成,局部變量區(qū)用于存放方法中的局部變量和參數(shù),操作數(shù)棧中用于存放方法執(zhí)行過(guò)程中產(chǎn)生的中間結(jié)果。
執(zhí)行過(guò)程如下
1、為main方法創(chuàng)建棧幀:
局部變量表長(zhǎng)度為2,slot0存放參數(shù)args,slot1存放局部變量Student s,操作數(shù)棧最大深度為5。
2、new#7指令,在java堆中創(chuàng)建一個(gè)Student對(duì)象,并將其引用值放入棧頂。
3、初始化一個(gè)對(duì)象(通過(guò)實(shí)例構(gòu)造的方式)
up指令:復(fù)制棧頂?shù)闹?,然后將?fù)制的結(jié)果入棧。
bipush 23:將單字節(jié)常量值23入棧。
ldc #8:將#8這個(gè)常量池中的常量即”dqrcsc”取出,并入棧。
ldc #9:將#9這個(gè)常量池中的常量即”20150723”取出,并入棧。
4、invokespecial #10:調(diào)用#10這個(gè)常量所代表的方法,即Student.()這個(gè)方法,這步是為了初始化對(duì)象s的各項(xiàng)值
《init》()方法,是編譯器將調(diào)用父類的《init》()的語(yǔ)句、構(gòu)造代碼塊、實(shí)例字段賦值語(yǔ)句,以及自己編寫(xiě)的構(gòu)造方法中的語(yǔ)句整合在一起生成的一個(gè)方法。保證調(diào)用父類的《init》()方法在最開(kāi)頭,自己編寫(xiě)的構(gòu)造方法語(yǔ)句在最后,而構(gòu)造代碼塊及實(shí)例字段賦值語(yǔ)句按出現(xiàn)的順序按序整合到《init》()方法中。
注意到Student.《init》()方法的最大操作數(shù)棧深度為3,局部變量表大小為4。
此時(shí)需注意:從dup到ldc #9這四條指令向棧中添加了4個(gè)數(shù)據(jù),而Student.()方法剛好也需要4個(gè)參數(shù):
public Student(int age, String name, String sid){
super(age,name);
this.sid = sid;
}1234567
雖然定義中只顯式地定義了傳入3個(gè)參數(shù),而實(shí)際上會(huì)隱含傳入一個(gè)當(dāng)前對(duì)象的引用作為第一個(gè)參數(shù),所以四個(gè)參數(shù)依次為this,age,name,sid。
上面的4條指令剛好把這四個(gè)參數(shù)的值依次入棧,進(jìn)行參數(shù)傳遞,然后調(diào)用了Student.《init》()方法,會(huì)創(chuàng)建該方法的棧幀,并入棧。棧幀中的局部變量表的第0到4個(gè)slot分別保存著入棧的那四個(gè)參數(shù)值。
創(chuàng)建Studet.《init》()方法的棧幀:
Student.《init》()方法中的字節(jié)碼指令:
aload_0:將局部變量表slot0處的引用值入棧
aload_1:將局部變量表slot1處的int值入棧
aload_2:將局部變量表slot2處的引用值入棧
invokespecial #1:調(diào)用Person.()方法,同調(diào)用Student.過(guò)程類似,創(chuàng)建棧幀,將三個(gè)參數(shù)的值存放到局部變量表等,這里就不畫(huà)圖了……
從Person.()返回之后,用于傳參的棧頂?shù)?個(gè)值被回收了。
aload_0:將slot0處的引用值入棧。
aload_3:將slot3處的引用值入棧。
putfield #2:將當(dāng)前棧頂?shù)闹怠?0150723”賦值給0x2222所引用對(duì)象的sid字段,然后棧中的兩個(gè)值出棧。
return:返回調(diào)用方即main()方法,當(dāng)前方法棧幀出棧。
重新回到main()方法中,繼續(xù)執(zhí)行下面的字節(jié)碼指令:
astore_1:將當(dāng)前棧頂引用類型的值賦值給slot1處的局部變量,然后出棧。
5、到這兒為止,第一行代碼執(zhí)行完畢,將s返回給局部變量表,執(zhí)行下邊的
public static void main(String[] args){
Student s = new Student(23,“dqrcsc”,“20150723”);//執(zhí)行完畢
s.study(5,6);
Student.getCnt();
s.run();
}1234567891011
aload_1:slot1處的引用類型的值入棧
iconst_5:將常數(shù)5入棧,int型常數(shù)只有0-5有對(duì)應(yīng)的iconst_x指令
bipush 6:將常數(shù)6入棧
6、開(kāi)始執(zhí)行第二行代碼,也就是strudy方法
invokevirtual #11:調(diào)用虛方法study(),這個(gè)方法是重寫(xiě)的接口中的方法,需要?jiǎng)討B(tài)分派,所以使用了invokevirtual指令。
創(chuàng)建study()方法的棧幀:
最大棧深度3,局部變量表5
方法的java源碼:
這里寫(xiě)代碼片public int study(int a, int b){
int c = 10;
int d = 20;
return a+b*c-d;
}123456789
bipush 10:將10入棧
istore_3:將棧頂?shù)?0賦值給slot3處的int局部變量,即c,出棧。
bipush 20:將20入棧
istore 4:將棧頂?shù)?0付給slot4處的int局部變量,即d,出棧。
上面4條指令,完成對(duì)c和d的賦值工作。
iload_1、iload_2、iload_3這三條指令將slot1、slot2、slot3這三個(gè)局部變量入棧:
imul:將棧頂?shù)膬蓚€(gè)值出棧,相乘的結(jié)果入棧:
iadd:將當(dāng)前棧頂?shù)膬蓚€(gè)值出棧,相加的結(jié)果入棧
iload 4:將slot4處的int型的局部變量入
isub:將棧頂兩個(gè)值出棧,相減結(jié)果入棧:
ireturn:將當(dāng)前棧頂?shù)闹捣祷氐秸{(diào)用方。
7、到這兒為止,第二行代碼執(zhí)行完畢,返回值返回給s,執(zhí)行下邊的
public static void main(String[] args){
Student s = new Student(23,“dqrcsc”,“20150723”);//執(zhí)行完畢
s.study(5,6);
Student.getCnt();
s.run();
}1234567891011
invokestatic #12 調(diào)用靜態(tài)方法getCnt()不需要傳任何參數(shù)
pop:getCnt()方法有返回值,將其出棧
aload_1:將slot1處的引用值入棧
invokevirtual #13:調(diào)用0x2222對(duì)象的run()方法,重寫(xiě)自父類的方法,需要?jiǎng)討B(tài)分派,所以使用invokevirtual指令
return:main()返回,程序運(yùn)行結(jié)束。
評(píng)論