JavaRush /Java Blog /Random-TW /在背景編譯和執行 Java 應用程式
Павел Голов
等級 34
Москва

在背景編譯和執行 Java 應用程式

在 Random-TW 群組發布

內容:

  1. 介紹
  2. 編譯為字節碼
  3. 程式編譯與執行範例
  4. 在虛擬機器上執行程式
  5. 即時 (JIT) 編譯
  6. 結論
在背景編譯和執行 Java 應用程式 - 1

一、簡介

大家好!今天,我想分享有關運行 Java 編寫的應用程式後 JVM(Java 虛擬機)底層發生的情況的知識。如今,有一些流行的開發環境可以幫助您避免考慮 JVM 的內部結構、編譯和執行 Java 程式碼,這可能會導致新開發人員錯過這些重要方面。同時,在訪談中經常會問到關於這個主題的問題,這就是我決定寫一篇文章的原因。

2.編譯為字節碼

在背景編譯和執行 Java 應用程式 - 2
讓我們從理論開始。當我們編寫任何應用程式時,我們都會建立一個帶有擴展名的文件.java,並用 Java 程式語言在其中放置程式碼。這種包含人類可讀程式碼的檔案稱為原始碼檔案。原始碼檔案準備好後,您需要執行它!但現階段它包含只有人類才能理解的資訊。Java 是一種多平台程式語言。這意味著用 Java 編寫的程式可以在任何安裝了專用 Java 運行時系統的平台上執行。這個系統稱為Java虛擬機器(JVM)。為了將程式從原始程式碼翻譯成 JVM 可以理解的程式碼,您需要對其進行編譯。JVM 所理解的程式碼稱為字節碼,包含虛擬機器隨後將執行的一組指令。javac為了將原始碼編譯為字節碼, JDK(Java Development Kit)中包含一個編譯器。作為輸入,編譯器接受一個擴展名為 的文件.java,其中包含程式的源代碼;作為輸出,它生成一個擴展名為 的文件.class,其中包含虛擬機執行程序所需的字節碼。一旦程式被編譯成字節碼,就可以使用虛擬機器來執行。

3. 程式編譯與執行範例

假設我們有一個簡單的程序,包含在一個檔案中Calculator.java,它接受 2 個數字命令列參數並列印它們相加的結果:
class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
為了將該程式編譯為字節碼,我們將javac在命令列上使用編譯器:
javac Calculator.java
編譯後,我們收到一個以字節碼作為輸出的文件Calculator.class,我們可以使用安裝在我們電腦上的java機器在命令列上使用java命令來執行該文件:
java Calculator 1 2
請注意,在檔案名稱之後,指定了 2 個命令列參數 - 數字 1 和 2。執行程式後,數字 3 將顯示在命令列上。在上面的範例中,我們有一個獨立的簡單類別。但是如果該類別位於某個套件中怎麼辦?讓我們模擬以下情況:建立目錄src/ru/javarush並將我們的類別放置在那裡。現在看起來像這樣(我們在文件的開頭添加了包名稱):
package ru.javarush;

class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
讓我們使用以下命令編譯這樣一個類別:
javac -d bin src/ru/javarush/Calculator.java
在這個例子中,我們使用了一個額外的編譯器選項-d bin,將編譯後的檔案放入一個bin結構類似目錄的目錄中src,但目錄bin必須提前建立。此技術用於避免將原始程式碼檔案與字節碼檔案混淆。在執行編譯的程式之前,有必要先解釋一下這個概念classpathClasspath是虛擬機器查找套件和編譯類別的相對路徑。也就是說,透過這種方式我們告訴虛擬機器檔案系統中哪些目錄是Java包層次結構的根。Classpath可以在啟動程式時使用標誌指定-classpath。我們使用以下命令啟動程式:
java -classpath ./bin ru.javarush.Calculator 1 2
在此範例中,我們需要類別的完整名稱,包括它所在的套件的名稱。最終的文件樹如下圖所示:
├── src
│     └── ru
│          └── javarush
│                  └── Calculator.java
└── bin
      └── ru
           └── javarush
                   └── Calculator.class

4. 虛擬機器執行程式

於是,我們啟動了書面程序。但是當虛擬機器啟動已編譯的程式時會發生什麼?首先我們先來了解編譯和程式碼解釋的概念是什麼意思。 編譯是將用高階原始語言編寫的程式翻譯成用類似機器碼的低階語言編寫的等效程式。 解釋是對原始程式或請求的逐個語句(逐行命令、逐行)分析、處理和立即執行(與編譯相反,編譯是翻譯程式而不執行它)。Java語言同時具有編譯器(javac)和解釋器,解釋器是一個虛擬機,它將字節碼逐行轉換為機器碼並立即執行。這樣,當我們執行一個編譯好的程式時,虛擬機器就開始對其進行解釋,即將字節碼逐行轉換為機器碼,並開始執行。不幸的是,純字節碼解釋是一個相當漫長的過程,並且使 java 比其競爭對手慢。為了避免這種情況,引入了一種機制來加速虛擬機器對字節碼的解釋。這種機制稱為即時編譯(JITC)。

5. 即時(JIT)編譯

簡單來說,Just-In-Time編譯的機制是這樣的:如果程式中存在多次執行的程式碼部分,那麼可以將它們一次編譯成機器碼,以加快以後的執行速度。將這部分程式編譯成機器碼後,後續每次呼叫這部分程式時,虛擬機器都會立即執行編譯後的機器碼,而不是解釋它,這自然會加快程式的執行速度。加快程式速度是透過增加記憶體消耗(我們需要將編譯後的機器碼儲存在某個地方!)以及增加程式執行期間​​編譯所花費的時間來實現的。JIT 編譯是一個相當複雜的機制,所以讓我們先簡單介紹一下。字節碼到機器碼的 JIT 編譯有 4 個等級。編譯等級越高,越複雜,但同時這樣的section的執行速度會比較低階的section更快。JIT - 編譯器根據每個程式片段的執行頻率來決定為該片段設定什麼編譯等級。在底層,JVM 使用 2 個 JIT 編譯器 - C1 和 C2。C1 編譯器也稱為客戶端編譯器,只能編譯第 3 級程式碼。C2 編譯器負責第四個、最複雜且最快的編譯等級。
在底層編譯和執行 Java 應用程式 - 3
從上面我們可以得出結論,對於簡單的客戶端應用程序,使用 C1 編譯器更有利可圖,因為在這種情況下,應用程式啟動的速度對我們來說很重要。伺服器端、長期存在的應用程式可能需要更長的時間來啟動,但將來它們必須快速工作並執行其功能 - 這裡 C2 編譯器適合我們。 當在 x32 版本的 JVM 上執行 Java 程式時,我們可以使用-client和標誌手動指定要使用的模式-server。當指定該標誌時,-clientJVM將不會執行複雜的字節碼優化,這將加快應用程式的啟動時間並減少記憶體消耗量。當指定該標誌時,-server由於複雜的字節碼優化,應用程式將需要更長的啟動時間,並且將使用更多的記憶體來儲存機器碼,但程式將來會運行得更快。在 x64 版本的 JVM 中,該標誌-client被忽略,預設使用應用程式伺服器配置。

六,結論

我對編譯和執行 Java 應用程式如何運作的簡要概述到此結束。要點:
  1. javac編譯器將程式的原始碼轉換為可以在安裝Java虛擬機器的任何平台上執行的字節碼;
  2. 編譯後,JVM 解釋產生的字節碼;
  3. 為了加快 Java 應用程式的速度,JVM 使用即時編譯機制,將程式中最常執行的部分轉換為機器碼並將其儲存在記憶體中。
我希望本文能幫助您更深入地了解我們最喜歡的程式語言的工作原理。感謝您的閱讀,歡迎批評指正!
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION