Java是編譯語言還是解釋語言?
1.概述
編程語言根據其抽象級別進行分類。我們區分高級語言(Java,Python,JavaScript,C ++,Go),低級語言(彙編程序),最後是機器代碼。
每個高級語言代碼(例如Java)都需要轉換為機器本地代碼才能執行。該翻譯過程可以是編譯或解釋。但是,還有第三種選擇。試圖利用兩種方法的組合。
在本教程中,我們將探討如何在多個平台上編譯和執行Java代碼。我們將研究一些Java和JVM設計細節。這些將幫助我們確定Java是編譯,解釋還是兩者的混合。
2.編譯與解釋
讓我們開始研究編譯和解釋編程語言之間的一些基本差異。
2.1。編譯語言
編譯器將編譯語言(C ++,Go)直接轉換為機器本機代碼。
在執行之前,它們需要明確的構建步驟。這就是為什麼每次更改代碼時都需要重建程序。
編譯語言往往比解釋語言更快,更有效**。**但是,它們生成的機器代碼是特定於平台的。
2.2。解釋型語言
另一方面,在解釋語言(Python,JavaScript)中,沒有構建步驟。相反,解釋器在執行程序時對程序的源代碼進行操作。
曾經認為口譯語言比編譯語言要慢得多。但是,隨著即時(JIT)編譯的發展,性能差距正在縮小。但是,我們應該注意,JIT編譯器在程序運行時將代碼從解釋語言轉換為機器本地代碼。
此外,我們可以在Windows,Linux或Mac等多個平台上執行解釋後的語言代碼。解釋的代碼與特定類型的CPU體系結構沒有關聯。
3.寫一次即可在任何地方運行
Java和JVM在設計時考慮了可移植性。因此,當今大多數流行的平台都可以運行Java代碼。
這聽起來似乎暗示著Java是一種純解釋性語言。但是,在執行之前,需要將Java源代碼編譯為字節碼。字節碼是JVM固有的一種特殊機器語言.
JVM在運行時解釋並執行此代碼。
是為支持Java的每個平台(而不是我們的程序或庫)構建和定制的JVM。
現代JVM也具有JIT編譯器。這意味著JVM在運行時優化我們的代碼,以獲得與編譯語言相似的性能優勢。
4. Java編譯器
javac命令行工具將Java源代碼編譯成包含平台無關字節碼的Java類文件:
$ javac HelloWorld.java
源代碼文件帶有.java
後綴,而包含字節碼的類文件則由生成。 class
後綴。
5. Java虛擬機
編譯的類文件(字節碼)可以由Java虛擬機(JVM)執行:
$ java HelloWorld Hello Java!
現在讓我們更深入地研究JVM體系結構。我們的目標是確定在運行時如何將字節碼轉換為機器本機代碼。
5.1。架構概述
JVM由五個子系統組成:
- 類加載器
- JVM內存
- 執行引擎
- 本機方法接口和
- 本機方法庫
5.2。類加載器
JVM利用ClassLoader子系統將已編譯的類文件帶入**JVM內存**。
除加載外,ClassLoader還執行鏈接和初始化。那包括:
- 驗證字節碼是否存在安全漏洞
- 為靜態變量分配內存
- 用原始引用替換符號內存引用
- 將原始值分配給靜態變量
- 執行所有靜態代碼塊
5.3。執行引擎
執行引擎子系統負責讀取字節碼,將其轉換為機器本機代碼並執行。
三個主要組件負責執行,包括解釋器和編譯器:
- 由於JVM與平台無關,因此它使用解釋器執行字節碼
- JIT編譯器通過將字節碼編譯為本地代碼以重複方法調用來提高性能
- 垃圾收集器收集並刪除所有未引用的對象
執行引擎利用本機方法接口(JNI)來調用本機庫和應用程序。
5.4。即時編譯器
解釋器的主要缺點是,每次調用方法時,都需要解釋,這可能比編譯的本機代碼慢。 Java使用JIT編譯器來克服此問題。
JIT編譯器不能完全替代解釋器。執行引擎仍在使用它。但是,JVM根據調用方法的頻率使用JIT編譯器。
JIT編譯器將整個方法的字節碼編譯為機器本機代碼,因此可以直接重用。與標準編譯器一樣,生成中間代碼,進行優化,然後生成機器本機代碼。
探查器是JIT編譯器的特殊組件,負責查找熱點。 JVM根據運行時收集的性能分析信息來決定要編譯的代碼。
這樣的效果是,經過幾個執行週期,Java程序可以更快地執行其工作。 JVM了解到熱點後,便可以創建本機代碼,從而使運行速度更快。
6.性能比較
讓我們看一下JIT編譯如何提高Java的運行時性能。
6.1。斐波那契性能測試
我們將使用一種簡單的遞歸方法來計算第n個斐波那契數:
private static int fibonacci(int index) {
if (index <= 1) {
return index;
}
return fibonacci(index-1) + fibonacci(index-2);
}
為了衡量重複方法調用的性能收益,我們將運行Fibonacci方法100次:
for (int i = 0; i < 100; i++) {
long startTime = System.nanoTime();
int result = fibonacci(12);
long totalTime = System.nanoTime() - startTime;
System.out.println(totalTime);
}
首先,我們將正常編譯並執行Java代碼:
$ java Fibonacci.java
然後,我們將在禁用JIT編譯器的情況下執行相同的代碼:
$ java -Djava.compiler=NONE Fibonacci.java
最後,我們將在C ++和JavaScript中實現並運行相同的算法進行比較。
6.2。性能測試結果
讓我們看一下運行Fibonacci遞歸測試後測得的平均性能(以納秒為單位):
- 使用JIT編譯器的Java – 2726 ns –最快
- 沒有JIT編譯器的Java – 17965 ns –慢559%
- 沒有O2優化的C ++ – 9435 ns –降低246%
- 具有O2優化的C ++ – 3639 ns –慢33%
- JavaScript – 22998 ns –慢743%
在此示例中,使用JIT編譯器, Java的性能提高了500%以上。但是,JIT編譯器確實需要運行一些才能運行。
有趣的是,即使在啟用O2優化標誌的情況下編譯C ++,Java的性能也比C ++代碼好33%。不出所料,當仍然解釋Java時, C ++在前幾次運行中的性能要好得多。
Java還勝過與Node一起運行的等效JavaScript代碼,後者也使用JIT編譯器。結果顯示性能提高了700%以上。主要原因是Java的JIT編譯器啟動速度更快。
7.要考慮的事情
從技術上講,可以將任何靜態編程語言代碼直接編譯為機器代碼。也可以逐步解釋任何編程代碼。
與許多其他現代編程語言類似,Java使用編譯器和解釋器的組合。目標是利用兩全其美,實現高性能和平台無關的執行。
在本文中,我們重點介紹了HotSpot中的工作方式。 HotSpot是Oracle默認的開源JVM實現。 Graal VM也基於HotSpot,因此適用相同的原理。
如今,最流行的JVM實現使用解釋器和JIT編譯器的組合。但是,其中一些可能使用其他方法。
8.結論
在本文中,我們研究了Java和JVM內部。我們的目標是確定Java是編譯語言還是解釋語言。我們探索了Java編譯器和JVM執行引擎的內部結構。
基於此,我們得出結論, Java使用了兩種方法的組合。
我們用Java編寫的源代碼在構建過程中首先被編譯為字節碼。然後,JVM解釋生成的字節碼以供執行。但是,JVM還在運行時使用JIT編譯器來提高性能。