久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

java拾遺4--一個(gè)簡(jiǎn)單java程序的運(yùn)行全過(guò)程

 格瑞思n5c5alhf 2018-08-30

簡(jiǎn)單說(shuō)來(lái),一個(gè)java程序的運(yùn)行需要編輯源碼,、編譯生成class文件,、加載class文件、解釋或編譯運(yùn)行class中的字節(jié)碼指令,。

下面有一段簡(jiǎn)單的java源碼,,通過(guò)它來(lái)看一下java程序的運(yùn)行流程:

復(fù)制代碼
 1 class Person
 2 
 3 {
 4 
 5        private String name;
 6 
 7        private int age;
 8 
 9  
10 
11        public Person(int age, String name){
12 
13               this.age = age;
14 
15               this.name = name;
16 
17        }
18 
19        public void run(){
20 
21              
22 
23        }
24 
25 }
26 
27  
28 
29 interface IStudyable
30 
31 {
32 
33        public int study(int a, int b);
34 
35 }
36 
37 public class Student extends Person implements IStudyable
38 
39 {
40 
41        private static int cnt=5;
42 
43        static{
44 
45               cnt++;
46 
47        }
48 
49        private String sid;
50 
51        public Student(int age, String name, String sid){
52 
53               super(age,name);
54 
55               this.sid = sid;
56 
57        }
58 
59        public void run(){
60 
61               System.out.println("run()...");
62 
63        }
64 
65        public int study(int a, int b){
66 
67               int c = 10;
68 
69               int d = 20;
70 
71               return a+b*c-d;
72 
73        }
74 
75        public static int getCnt(){
76 
77               return cnt;
78 
79        }
80 
81        public static void main(String[] args){
82 
83               Student s = new Student(23,"dqrcsc","20150723");
84 
85               s.study(5,6);
86 
87               Student.getCnt();
88 
89               s.run();
90 
91        }
92 
93 }
復(fù)制代碼

1.編輯源碼:無(wú)論是使用記事本還是別的什么,編寫上面的代碼,,然后保存到Student.java,,我直接就放到桌面了

 

2.編譯生成class字節(jié)碼文件:

在桌面上,,按住shift,,然后按下鼠標(biāo)右鍵:

 

點(diǎn)擊“在此處打開命令窗口”

 

輸入命令javac Student.java將該源碼文件編譯生成.class字節(jié)碼文件。

由于在源碼文件中定義了兩個(gè)類,,一個(gè)接口,,所以生成了3個(gè).clsss文件:

 

這樣能在java虛擬機(jī)上運(yùn)行的字節(jié)碼文件就生成了

3.啟動(dòng)java虛擬機(jī)運(yùn)行字節(jié)碼文件:

 

在命令行中輸入java Student這個(gè)命令,就啟動(dòng)了一個(gè)java虛擬機(jī),,然后加載Student.class字節(jié)碼文件到內(nèi)存,,然后運(yùn)行內(nèi)存中的字節(jié)碼指令了。

 

我們從編譯到運(yùn)行java程序,,只輸入了兩個(gè)命令,,甚至,如果使用集成開發(fā)環(huán)境,如eclipse,,只要ctrl+s保存就完成了增量編譯,,只需要按下一個(gè)按鈕就運(yùn)行了java程序。但是,,在這些簡(jiǎn)單操作的背后還有一些操作……

 

 

1.從源碼到字節(jié)碼:

字節(jié)碼文件,,看似很微不足道的東西,卻真正實(shí)現(xiàn)了java語(yǔ)言的跨平臺(tái),。各種不同平臺(tái)的虛擬機(jī)都統(tǒng)一使用這種相同的程序存儲(chǔ)格式,。更進(jìn)一步說(shuō),jvm運(yùn)行的是class字節(jié)碼文件,,只要是這種格式的文件就行,,所以,實(shí)際上jvm并不像我之前想象地那樣與java語(yǔ)言緊緊地捆綁在一起,。如果非常熟悉字節(jié)碼的格式要求,,可以使用二進(jìn)制編輯器自己寫一個(gè)符合要求的字節(jié)碼文件,然后交給jvm去運(yùn)行,;或者把其他語(yǔ)言編寫的源碼編譯成字節(jié)碼文件,,交給jvm去運(yùn)行,只要是合法的字節(jié)碼文件,,jvm都會(huì)正確地跑起來(lái),。所以,它還實(shí)現(xiàn)了跨語(yǔ)言……

通過(guò)jClassLib可以直接查看一個(gè).class文件中的內(nèi)容,,也可以給JDK中的javap命令指定參數(shù),,來(lái)查看.class文件的相關(guān)信息:

javap –v Student

 

好多輸出,在命令行窗口查看不是太方便,,可以輸出重定向下:

       javap –v Student > Student.class.txt

 

桌面上多出了一個(gè)Student.class.txt文件,,里面存放著便于閱讀的Student.class文件中相關(guān)的信息

 

 

部分class文件內(nèi)容,從上面圖中,,可以看到這些信息來(lái)自于Student.class,,編譯自Student.java,編譯器的主版本號(hào)是52,,也就是jdk1.8,,這個(gè)類是public,然后是存放類中常量的常量池,,各個(gè)方法的字節(jié)碼等,,這里就不一一記錄了。

       總之,,我想說(shuō)的就是字節(jié)碼文件很簡(jiǎn)單很強(qiáng)大,,它存放了這個(gè)類的各種信息:字段,、方法、父類,、實(shí)現(xiàn)的接口等各種信息,。

 

2.Java虛擬機(jī)的基本結(jié)構(gòu)及其內(nèi)存分區(qū)

       Java虛擬機(jī)要運(yùn)行字節(jié)碼指令,就要先加載字節(jié)碼文件,,誰(shuí)來(lái)加載,,怎么加載,加載到哪里……誰(shuí)來(lái)運(yùn)行,,怎么運(yùn)行,,同樣也要考慮……

 

上面是一個(gè)JVM的基本結(jié)構(gòu)及內(nèi)存分區(qū)的圖,有點(diǎn)抽象,,有點(diǎn)丑……簡(jiǎn)單說(shuō)明下:

       JVM中把內(nèi)存分為直接內(nèi)存,、方法區(qū)、Java棧,、Java堆,、本地方法棧、PC寄存器等,。

       直接內(nèi)存:就是原始的內(nèi)存區(qū)

       方法區(qū):用于存放類,、接口的元數(shù)據(jù)信息,加載進(jìn)來(lái)的字節(jié)碼數(shù)據(jù)都存儲(chǔ)在方法區(qū)

       Java棧:執(zhí)行引擎運(yùn)行字節(jié)碼時(shí)的運(yùn)行時(shí)內(nèi)存區(qū),,采用棧幀的形式保存每個(gè)方法的調(diào)用運(yùn)行數(shù)據(jù)

       本地方法棧:執(zhí)行引擎調(diào)用本地方法時(shí)的運(yùn)行時(shí)內(nèi)存區(qū)

       Java堆:運(yùn)行時(shí)數(shù)據(jù)區(qū),,各種對(duì)象一般都存儲(chǔ)在堆上

       PC寄存器:功能如同CPU中的PC寄存器,指示要執(zhí)行的字節(jié)碼指令,。

       JVM的功能模塊主要包括類加載器,、執(zhí)行引擎和垃圾回收系統(tǒng)

3.類加載器加載Student.class到內(nèi)存

       1)類加載器會(huì)在指定的classpath中找到Student.class這個(gè)文件,,然后讀取字節(jié)流中的數(shù)據(jù),,將其存儲(chǔ)在方法區(qū)中。

       2)會(huì)根據(jù)Student.class的信息建立一個(gè)Class對(duì)象,,這個(gè)對(duì)象比較特殊,,一般也存放在方法區(qū)中,用于作為運(yùn)行時(shí)訪問(wèn)Student類的各種數(shù)據(jù)的接口,。

       3)必要的驗(yàn)證工作,,格式、語(yǔ)義等

       4)為Student中的靜態(tài)字段分配內(nèi)存空間,,也是在方法區(qū)中,并進(jìn)行零初始化,,即數(shù)字類型初始化為0,,boolean初始化為false,,引用類型初始化為null等。

在Student.java中只有一個(gè)靜態(tài)字段:

 private static int cnt=5; 

此時(shí),,并不會(huì)執(zhí)行賦值為5的操作,,而是將其初始化為0。

       5)由于已經(jīng)加載到內(nèi)存了,,所以原來(lái)字節(jié)碼文件中存放的部分方法,、字段等的符號(hào)引用可以解析為其在內(nèi)存中的直接引用了,而不一定非要等到真正運(yùn)行時(shí)才進(jìn)行解析,。

       6)在編譯階段,,編譯器收集所有的靜態(tài)字段的賦值語(yǔ)句及靜態(tài)代碼塊,并按語(yǔ)句出現(xiàn)的順序拼接出一個(gè)類初始化方法<clinit>(),。此時(shí),,執(zhí)行引擎會(huì)調(diào)用這個(gè)方法對(duì)靜態(tài)字段進(jìn)行代碼中編寫的初始化操作。

在Student.java中關(guān)于靜態(tài)字段的賦值及靜態(tài)代碼塊有兩處:

復(fù)制代碼
1 private static int cnt=5;
2 
3 static{
4 
5           cnt++;
6 
7 }
復(fù)制代碼

 

將按出現(xiàn)順序拼接,,形式如下:

復(fù)制代碼
1 void <clinit>(){
2 
3               cnt = 5;
4 
5               cnt++;
6 
7 }
復(fù)制代碼

可以通過(guò)jClassLib這個(gè)工具看到生成的<clinit>()方法的字節(jié)碼指令:

 

iconst_5指令把常數(shù)5入棧

putstatic #6將棧頂?shù)?賦值給Student.cnt這個(gè)靜態(tài)字段

getstatic #6 獲取Student.cnt這個(gè)靜態(tài)字段的值,,并將其放入棧頂

iconst_1 把常數(shù)1入棧

iadd 取出棧頂?shù)膬蓚€(gè)整數(shù),相加,,結(jié)果入棧

putstatic #6 取出棧頂?shù)恼麛?shù),,賦值給Student.cnt

return 從當(dāng)前方法中返回,沒(méi)有任何返回值,。

從字節(jié)碼來(lái)看,,確實(shí)先后執(zhí)行了cnt =5 及 cnt++這兩行代碼。

 

在這里有一點(diǎn)要注意的是,,這里籠統(tǒng)的描述了下類的加載及初始化過(guò)程,,但是,實(shí)際中,,有可能只進(jìn)行了類加載,,而沒(méi)有進(jìn)行初始化工作,原因就是在程序中并沒(méi)有訪問(wèn)到該類的字段及方法等,。

此外,,實(shí)際加載過(guò)程也會(huì)相對(duì)來(lái)說(shuō)比較復(fù)雜,一個(gè)類加載之前要加載它的父類及其實(shí)現(xiàn)的接口:加載的過(guò)程可以通過(guò)java –XX:+TraceClassLoading參數(shù)查看:

如:java -XX:+TraceClassLoading Student,,信息太多,,可以重定向下:

 

查看輸出的loadClass.txt文件:

 

可以看到最先加載的是Object.class這個(gè)類,當(dāng)然了,,所有類的父類,。

 

直到第390行才看到自己定義的部分被加載,先是Student實(shí)現(xiàn)的接口IStudyable,,然后是其父類Person,,然后才是Student自身,,然后是一個(gè)啟動(dòng)類的加載,然后就是找到main()方法,,執(zhí)行了,。

4.執(zhí)行引擎找到main()這個(gè)入口方法,執(zhí)行其中的字節(jié)碼指令:

要了解方法的運(yùn)行,,需要先稍微了解下java棧:

JVM中通過(guò)java棧,,保存方法調(diào)用運(yùn)行的相關(guān)信息,每當(dāng)調(diào)用一個(gè)方法,,會(huì)根據(jù)該方法的在字節(jié)碼中的信息為該方法創(chuàng)建棧幀,,不同的方法,其棧幀的大小有所不同,。棧幀中的內(nèi)存空間還可以分為3塊,,分別存放不同的數(shù)據(jù):

局部變量表:存放該方法調(diào)用者所傳入的參數(shù),及在該方法的方法體中創(chuàng)建的局部變量,。

操作數(shù)棧:用于存放操作數(shù)及計(jì)算的中間結(jié)果等,。

其他棧幀信息:如返回地址、當(dāng)前方法的引用等,。

只有當(dāng)前正在運(yùn)行的方法的棧幀位于棧頂,,當(dāng)前方法返回,則當(dāng)前方法對(duì)應(yīng)的棧幀出棧,,當(dāng)前方法的調(diào)用者的棧幀變?yōu)闂m?;?dāng)前方法的方法體中若是調(diào)用了其他方法,則為被調(diào)用的方法創(chuàng)建棧幀,,并將其壓入棧頂,。

 

注意:局部變量表及操作數(shù)棧的最大深度在編譯期間就已經(jīng)確定了,存儲(chǔ)在該方法字節(jié)碼的Code屬性中,。

簡(jiǎn)單查看Student.main()的運(yùn)行過(guò)程:

簡(jiǎn)單看下main()方法:

復(fù)制代碼
 1 public static void main(String[] args){
 2 
 3               Student s = new Student(23,"dqrcsc","20150723");
 4 
 5               s.study(5,6);
 6 
 7               Student.getCnt();
 8 
 9               s.run();
10 
11 }
復(fù)制代碼

對(duì)應(yīng)的字節(jié)碼,,兩者對(duì)照著看起來(lái)更易于理解些:

 

注意main()方法的這幾個(gè)信息:

 

Mximum stack depth指定當(dāng)前方法即main()方法對(duì)應(yīng)棧幀中的操作數(shù)棧的最大深度,當(dāng)前值為5

Maximum local variables指定main()方法中局部變量表的大小,,當(dāng)前為2,,及有兩個(gè)slot用于存放方法的參數(shù)及局部變量。

Code length指定main()方法中代碼的長(zhǎng)度,。

開始模擬main()中一條條字節(jié)碼指令的運(yùn)行:

創(chuàng)建棧幀:

 

局部變量表長(zhǎng)度為2,,slot0存放參數(shù)args,slot1存放局部變量Student s,,操作數(shù)棧最大深度為5,。

 

new #7指令:在java堆中創(chuàng)建一個(gè)Student對(duì)象,并將其引用值放入棧頂。

 

dup指令:復(fù)制棧頂?shù)闹?,然后將?fù)制的結(jié)果入棧,。

bipush 23:將單字節(jié)常量值23入棧,。

ldc #8:將#8這個(gè)常量池中的常量即”dqrcsc”取出,,并入棧。

ldc #9:將#9這個(gè)常量池中的常量即”20150723”取出,,并入棧,。

 

invokespecial #10:調(diào)用#10這個(gè)常量所代表的方法,即Student.<init>()這個(gè)方法

 

<init>()方法,,是編譯器將調(diào)用父類的<init>()的語(yǔ)句,、構(gòu)造代碼塊、實(shí)例字段賦值語(yǔ)句,,以及自己編寫的構(gòu)造方法中的語(yǔ)句整合在一起生成的一個(gè)方法,。保證調(diào)用父類的<init>()方法在最開頭,自己編寫的構(gòu)造方法語(yǔ)句在最后,,而構(gòu)造代碼塊及實(shí)例字段賦值語(yǔ)句按出現(xiàn)的順序按序整合到<init>()方法中,。

 

注意到Student.<init>()方法的最大操作數(shù)棧深度為3,局部變量表大小為4,。

此時(shí)需注意:從dup到ldc #9這四條指令向棧中添加了4個(gè)數(shù)據(jù),,而Student.<init>()方法剛好也需要4個(gè)參數(shù):

復(fù)制代碼
1 public Student(int age, String name, String sid){
2 
3               super(age,name);
4 
5               this.sid = sid;
6 
7 }
復(fù)制代碼

雖然定義中只顯式地定義了傳入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.<init>()方法,同調(diào)用Student.<init>過(guò)程類似,,創(chuàng)建棧幀,,將三個(gè)參數(shù)的值存放到局部變量表等,這里就不畫圖了……

從Person.<init>()返回之后,,用于傳參的棧頂?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處的局部變量,,然后出棧,。

 

aload_1:slot1處的引用類型的值入棧

iconst_5:將常數(shù)5入棧,int型常數(shù)只有0-5有對(duì)應(yīng)的iconst_x指令

bipush 6:將常數(shù)6入棧

 

invokevirtual #11:調(diào)用虛方法study(),,這個(gè)方法是重寫的接口中的方法,,需要?jiǎng)討B(tài)分派,所以使用了invokevirtual指令,。

創(chuàng)建study()方法的棧幀:

 

最大棧深度3,,局部變量表5

 

方法的java源碼:

public int study(int a, int b){

              int c = 10;

              int d = 20;

              return a+b*c-d;

}

對(duì)應(yīng)的字節(jié)碼:

 

注意到這里,通過(guò)jClassLib工具查看的字節(jié)碼指令有點(diǎn)問(wèn)題,,與源碼有偏差……

改用通過(guò)命令javap –v Student查看study()的字節(jié)碼指令:

 

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)用方,。

 

重新回到main()方法中:

pop指令,將study()方法的返回值出棧

invokestatic #12 調(diào)用靜態(tài)方法getCnt()不需要傳任何參數(shù)

pop:getCnt()方法有返回值,,將其出棧

aload_1:將slot1處的引用值入棧

invokevirtual #13:調(diào)用0x2222對(duì)象的run()方法,,重寫自父類的方法,需要?jiǎng)討B(tài)分派,,所以使用invokevirtual指令

return:main()返回,,程序運(yùn)行結(jié)束。

 

以上,就是一個(gè)簡(jiǎn)單程序運(yùn)行的大致過(guò)程,,只是今天看書的一些理解,,也許有錯(cuò)誤的地方,希望不會(huì)貽笑大方……

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多