Java編譯期和運行期 & JVM__Java

來源:互聯網
上載者:User

 Java整個編譯以及啟動並執行過程相當繁瑣,本文通過一個簡單的程式來簡單的說明整個流程。      

首先兩張圖,描述編譯和執行的過程:

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

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


          如下圖,Java程式從源檔案建立到程式運行要經過兩大步驟:1、源檔案由編譯器編譯成位元組碼(ByteCode)  2、位元組碼由java虛擬機器解釋運行。因為java程式既要編譯同時也要經過JVM的解釋運行,所以說Java被稱為半解釋語言( "semi-interpreted" language)。


圖1   java程式編譯運行過程

 

        下面通過以下這個java程式,來說明java程式從編譯到最後啟動並執行整個流程。代碼如下:

  Java代碼   //MainApp.java   public class MainApp {       public static void main(String[] args) {           Animal animal = new Animal("Puppy");           animal.printName();       }   }   //Animal.java   public class Animal {       public String name;       public Animal(String name) {           this.name = name;       }       public void printName() {           System.out.println("Animal ["+name+"]");       }   }  

 

        第一步(編譯): 建立完源檔案之後,程式會先被編譯為.class檔案。Java編譯一個類時,如果這個類所依賴的類還沒有被編譯,編譯器就會先編譯這個被依賴的類,然後引用,否則直接引用,這個有點象make。如果java編譯器在指定目錄下找不到該類所其依賴的類的.class檔案或者.java源檔案的話,編譯器話報“cant find symbol”的錯誤。         編譯後的位元組碼檔案格式主要分為兩部分:常量池方法位元組碼。常量池記錄的是代碼出現過的所有token(類名,成員變數名等等)以及符號引用(方法引用,成員變數引用等等);方法位元組碼放的是類中各個方法的位元組碼。下面是MainApp.class通過反組譯碼的結果,我們可以清楚看到.class檔案的結構: 圖2  MainApp類常量池  
圖3  MainApp類方法位元組碼          

最後產生的class檔案由以下部分組成: 結構資訊。包括class檔案格式版本號碼及各部分的數量與大小的資訊 中繼資料。對應於Java源碼中聲明與常量的資訊。包含類/繼承的超類/實現的介面的聲明資訊、域與方法聲明資訊和常量池 方法資訊。對應Java源碼中語句和運算式對應的資訊。包含位元組碼、異常處理器表、求值棧與局部變數區大小、求值棧的類型記錄、偵錯符號資訊
  第二步(運行):java類啟動並執行過程大概可分為兩個過程:1、類的載入  2、類的執行。需要說明的是:JVM主要在程式第一次主動使用類的時候,才會去載入該類。也就是說,JVM並不是在一開始就把一個程式就所有的類都載入到記憶體中,而是到不得不用的時候才把它載入進來,而且只載入一次。         下面是程式啟動並執行詳細步驟: 在編譯好java程式得到MainApp.class檔案後,在命令列上敲java AppMain。系統就會啟動一個jvm進程,jvm進程從classpath路徑中找到一個名為AppMain.class的二進位檔案,將MainApp的類資訊載入到運行時資料區的方法區內,這個過程叫做MainApp類的載入。 然後JVM找到AppMain的主函數入口,開始執行main函數。 main函數的第一條命令是Animal  animal = new Animal("Puppy");就是讓JVM建立一個Animal對象,但是這時候方法區中沒有Animal類的資訊,所以JVM馬上載入Animal類,把Animal類的類型資訊放到方法區中。 載入完Animal類之後,Java虛擬機器做的第一件事情就是在堆區中為一個新的Animal執行個體分配記憶體, 然後調用建構函式初始化Animal執行個體,這個Animal執行個體持有著指向方法區的Animal類的類型資訊(其中包含有方法表,java動態綁定的底層實現)的引用。 當使用animal.printName()的時候,JVM根據animal引用找到Animal對象,然後根據Animal對象持有的引用定位到方法區中Animal類的類型資訊的方法表,獲得printName()函數的位元組碼的地址。 開始運行printName()函數。
 圖4   java程式運行過程 特別說明:java類中所有public和protected的執行個體方法都採用動態綁定機制,所有私人方法、靜態方法、構造器及初始化方法<clinit>都是採用靜態繫結機制。而使用動態綁定機制的時候會用到方法表,靜態繫結時並不會用到。

Ps:

 方法重載:這個是發生在編譯時間的。方法重載也被稱為編譯時間多態,因為編譯器可以根據參數的類型來選擇使用哪個方法。

1 2 3 4 public class {       public static void evaluate(String param1);  // method #1       public static void evaluate( int param1);   // method #2 }

如果編譯器要編譯下面的語句的話:

1 evaluate(“My Test Argument passed to param1”);

它會根據傳入的參數是字串常量,產生調用#1方法的位元組碼

 

方法覆蓋:這個是在運行時發生的。方法重載被稱為運行時多態,因為在編譯期編譯器不知道並且沒法知道該去調用哪個方法。JVM會在代碼啟動並執行時候做出決定。

1 2 3 4 5 6 7 8 9 10 11 12 public class A {     public int compute( int input) {          //method #3          return 3 * input;     }        }   public class B extends A {     @Override     public int compute( int input) {          //method #4          return 4 * input;     }        }

子類B中的compute(..)方法重寫了父類的compute(..)方法。如果編譯器遇到下面的代碼:

1 2 3 public int evaluate(A reference, int arg2)  {       int result = reference.compute(arg2); }

編譯器是沒法知道傳入的參數reference的類型是A還是B。因此,只能夠在運行時,根據賦給輸入變數“reference”的對象的類型(例如,A或者B的執行個體)來決定調用方法#3還是方法#4.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.