java方法執行流程解析


Java程序運行時,必須經過編譯和運行兩個步驟。首先將后綴名為.java的源文件進行編譯,最終生成后綴名為.class的字節碼文件。然后Java虛擬機將編譯好的字節碼文件加載到內存(這個過程被稱為類加載,是由加載器完成的),然后虛擬機針對加載到內存的java類進行解釋執行,顯示結果。

Java的運行原理

在Java中引入了虛擬機的概念,即在機器和編譯程序之間加入了一層抽象的虛擬的機器。這台虛擬的機器在任何平台上都提供給編譯程序一個的共同的接口。編譯程序只需要面向虛擬機,生成虛擬機能夠理解的代碼,然后由解釋器來將虛擬機代碼轉換為特定系統的機器碼執行。在Java中,這種供虛擬機理解的代碼叫做字節碼(ByteCode),它不面向任何特定的處理器,只面向虛擬機。每一種平台的解釋器是不同的,但是實現的虛擬機是相同的。Java源程序經過編譯器編譯后變成字節碼,字節碼由虛擬機解釋執行,虛擬機將每一條要執行的字節碼送給解釋器,解釋器將其翻譯成特定機器上的機器碼,然后在特定的機器上運行。

Java代碼編譯和執行的整個過程

Java代碼編譯是由Java源碼編譯器來完成,流程圖如下所示:

java程序的執行過程詳解

Java字節碼的執行是由JVM執行引擎來完成,流程圖如下所示:

java程序的執行過程詳解

Java代碼編譯和執行的整個過程包含了以下三個重要的機制:

Java源碼編譯機制

類加載機制

類執行機制

Java源碼編譯機制

Java 源碼編譯由以下三個過程組成:(javac –verbose 輸出有關編譯器正在執行的操作的消息)

分析和輸入到符號表

注解處理

語義分析和生成class文件

java程序的執行過程詳解

最后生成的class文件由以下部分組成:

1、結構信息。包括class文件格式版本號及各部分的數量與大小的信息

2、元數據。對應於Java源碼中聲明與常量的信息。包含類/繼承的超類/實現的接口的聲明信息、域與方法聲明信息和常量池

3、方法信息。對應Java源碼中語句和表達式對應的信息。包含字節碼、異常處理器表、求值棧與局部變量區大小、求值棧的類型記錄、調試符號信息

類加載機制

JVM的類加載是通過ClassLoader及其子類來完成的,類的層次關系和加載順序可以由下圖來描述:

java程序的執行過程詳解

1)Bootstrap ClassLoader /啟動類加載器

$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實現,不是ClassLoader子類

2)Extension ClassLoader/擴展類加載器

負責加載java平台中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

3)App ClassLoader/ 系統類加載器

負責記載classpath中指定的jar包及目錄中class

4)Custom ClassLoader/用戶自定義類加載器(java.lang.ClassLoader的子類)

屬於應用程序根據自身需要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規范自行實現ClassLoader

加載過程中會先檢查類是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個classloader已加載就視為已加載此類,保證此類只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來逐層嘗試加載此類。

類加載雙親委派機制介紹和分析

在這里,需要着重說明的是,JVM在加載類時默認采用的是雙親委派機制。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。

類執行機制

JVM是基於棧的體系結構來執行class字節碼的。線程創建后,都會產生程序計數器(PC)和棧(Stack),程序計數器存放下一條要執行的指令在方法內的偏移量,棧中存放一個個棧幀,每個棧幀對應着每個方法的每次調用,而棧幀又是有局部變量區和操作數棧兩部分組成,局部變量區用於存放方法中的局部變量和參數,操作數棧中用於存放方法執行過程中產生的中間結果。

 

 

public static void main(String[] args){

Student s = new Student(23,“dqrcsc”,“20150723”);//執行完畢

s.study(5,6);

Student.getCnt();

s.run();

}

執行過程如下

1、為main方法創建棧幀:

java程序的執行過程詳解

局部變量表長度為2,slot0存放參數args,slot1存放局部變量Student s,操作數棧最大深度為5。

2、new#7指令,在java堆中創建一個Student對象,並將其引用值放入棧頂。

java程序的執行過程詳解

3、初始化一個對象(通過實例構造的方式)

up指令:復制棧頂的值,然后將復制的結果入棧。

bipush 23:將單字節常量值23入棧。

ldc #8:將#8這個常量池中的常量即”dqrcsc”取出,並入棧。

ldc #9:將#9這個常量池中的常量即”20150723”取出,並入棧。

java程序的執行過程詳解

4、invokespecial #10:調用#10這個常量所代表的方法,即Student.()這個方法,這步是為了初始化對象s的各項值

《init》()方法,是編譯器將調用父類的《init》()的語句、構造代碼塊、實例字段賦值語句,以及自己編寫的構造方法中的語句整合在一起生成的一個方法。保證調用父類的《init》()方法在最開頭,自己編寫的構造方法語句在最后,而構造代碼塊及實例字段賦值語句按出現的順序按序整合到《init》()方法中。

java程序的執行過程詳解

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

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

public Student(int age, String name, String sid){

super(age,name);

this.sid = sid;

}

雖然定義中只顯式地定義了傳入3個參數,而實際上會隱含傳入一個當前對象的引用作為第一個參數,所以四個參數依次為this,age,name,sid。

上面的4條指令剛好把這四個參數的值依次入棧,進行參數傳遞,然后調用了Student.《init》()方法,會創建該方法的棧幀,並入棧。棧幀中的局部變量表的第0到4個slot分別保存着入棧的那四個參數值。

創建Studet.《init》()方法的棧幀:

java程序的執行過程詳解

Student.《init》()方法中的字節碼指令:

java程序的執行過程詳解

aload_0:將局部變量表slot0處的引用值入棧

aload_1:將局部變量表slot1處的int值入棧

aload_2:將局部變量表slot2處的引用值入棧

java程序的執行過程詳解

invokespecial #1:調用Person.()方法,同調用Student.過程類似,創建棧幀,將三個參數的值存放到局部變量表等,這里就不畫圖了……

從Person.()返回之后,用於傳參的棧頂的3個值被回收了。

aload_0:將slot0處的引用值入棧。

aload_3:將slot3處的引用值入棧。

java程序的執行過程詳解

putfield #2:將當前棧頂的值”20150723”賦值給0x2222所引用對象的sid字段,然后棧中的兩個值出棧。

return:返回調用方即main()方法,當前方法棧幀出棧。

重新回到main()方法中,繼續執行下面的字節碼指令:

astore_1:將當前棧頂引用類型的值賦值給slot1處的局部變量,然后出棧。

java程序的執行過程詳解

5、到這兒為止,第一行代碼執行完畢,將s返回給局部變量表,執行下邊的

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

iconst_5:將常數5入棧,int型常數只有0-5有對應的iconst_x指令

bipush 6:將常數6入棧

java程序的執行過程詳解

6、開始執行第二行代碼,也就是strudy方法

invokevirtual #11:調用虛方法study(),這個方法是重寫的接口中的方法,需要動態分派,所以使用了invokevirtual指令。

創建study()方法的棧幀:

java程序的執行過程詳解

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

java程序的執行過程詳解

方法的java源碼:

這里寫代碼片public int study(int a, int b){

int c = 10;

int d = 20;

return a+b*c-d;

}123456789

java程序的執行過程詳解

bipush 10:將10入棧

istore_3:將棧頂的10賦值給slot3處的int局部變量,即c,出棧。

bipush 20:將20入棧

istore 4:將棧頂的20付給slot4處的int局部變量,即d,出棧。

上面4條指令,完成對c和d的賦值工作。

iload_1、iload_2、iload_3這三條指令將slot1、slot2、slot3這三個局部變量入棧:

java程序的執行過程詳解

imul:將棧頂的兩個值出棧,相乘的結果入棧:

java程序的執行過程詳解

iadd:將當前棧頂的兩個值出棧,相加的結果入棧

iload 4:將slot4處的int型的局部變量入

java程序的執行過程詳解

isub:將棧頂兩個值出棧,相減結果入棧:

ireturn:將當前棧頂的值返回到調用方。

java程序的執行過程詳解

7、到這兒為止,第二行代碼執行完畢,返回值返回給s,執行下邊的

public static void main(String[] args){

Student s = new Student(23,“dqrcsc”,“20150723”);//執行完畢

s.study(5,6);

Student.getCnt();

s.run();

}1234567891011

invokestatic #12 調用靜態方法getCnt()不需要傳任何參數

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

aload_1:將slot1處的引用值入棧

invokevirtual #13:調用0x2222對象的run()方法,重寫自父類的方法,需要動態分派,所以使用invokevirtual指令

return:main()返回,程序運行結束。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM