Java虛擬機器(JVM)JIT編譯器


在本章中,我們將學習JIT編譯器,以及編譯語言和解釋語言之間的區別。

編譯語言與解釋語言

C,C++和FORTRAN等語言是編譯語言。程式碼以二進位制程式碼的形式提供,目標是底層機器。高階程式碼由專門為底層架構編寫的靜態編譯器一次編譯成二進位制程式碼。生成的二進位制檔案不能在任何其他體系結構上執行。

另一方面,Python和Perl等解釋語言可以在任何機器上執行,只要它們具有有效的直譯器即可。它在高階程式碼上逐行進行,將其轉換為二進位制程式碼。

解釋程式碼通常比編譯程式碼慢。例如,考慮一個迴圈。解釋將轉換回圈的每次疊代的相應程式碼。另一方面,編譯程式碼翻譯只生成一個二進位制檔案。直譯器一次只能看到一行,因此無法執行任何重要的程式碼,例如,更改編譯器等語句的執行順序。

我們將在下面研究優化的例子 -

新增儲存在記憶體中的兩個數位。由於存取記憶體可能會消耗多個CPU週期,因此良好的編譯器將發出指令以從記憶體中獲取資料,並僅在資料可用時執行新增。它不會等待,同時執行其他指令。另一方面,在解釋期間不可能進行這樣的優化,因為直譯器在任何給定時間都不知道整個程式碼。

但是,解釋語言可以在任何具有該語言的有效直譯器的機器上執行。

Java是編譯還是解釋語言?

Java試圖找到一個中間立場。由於JVM位於javac編譯器和底層硬體之間,因此javac(或任何其他編譯器)編譯器在Bytecode中編譯Java程式碼,這是由特定於平台的JVM所理解的。然後,當程式碼執行時,JVM使用JIT(即時)編譯以二進位制編譯位元組碼。

熱點(HotSpots)

在典型的程式中,只有一小部分程式碼經常執行,而且通常,這段程式碼會顯著影響整個應用程式的效能。這些程式碼段稱為HotSpots

如果某些程式碼段只執行一次,那麼編譯它將是一種浪費,而且解釋位元組碼會更快。但是如果該部分是一個熱點(HotSpots)部分並且執行多次,那麼JVM將編譯它。例如,如果一個方法被多次呼叫,那麼編譯程式碼所需的額外週期將生成的更快的二進位制檔案所抵消。

此外,JVM執行特定方法或迴圈越多,它收集的資訊越多,以進行各種優化,從而生成更快的二進位制檔案。

考慮以下程式碼 -

for(int i = 0 ; I <= 100; i++) {
   System.out.println(obj1.equals(obj2)); //two objects
}

這個程式碼中,直譯器會為每次疊代推匯出obj1類。這是因為Java中的每個類都有一個equals()方法,它從Object類擴充套件並可以覆蓋。
另一方面,實際發生的是JVM會注意到,對於每次疊代,obj1都是String類,因此,它會直接生成與String類的equals()方法對應的程式碼。因此不需要查詢,並且編譯的程式碼將執行得更快。

只有當JVM知道程式碼的行為時,才能實現這種行為。因此,它在編譯程式碼的某些部分之前等待。

以下是另一個例子 -

int sum = 7;
for(int i = 0 ; i <= 100; i++) {
   sum += i;
}

對於每個迴圈,直譯器從記憶體中獲取sum的值,向其新增I,並將其儲存回記憶體。記憶體存取是一項昂貴的操作,通常需要多個CPU週期。由於此程式碼多次執行,因此它是HotSpot。JIT將編譯此程式碼並進行以下優化。

sum的本地副本將儲存在特定於特定執行緒的暫存器中。所有操作都將對暫存器中的值進行,當迴圈完成時,該值將寫回記憶體。

如果其他執行緒也在存取變數怎麼辦?由於某些其他執行緒正在對變數的本地副本進行更新,因此它們會看到過時的值。在這種情況下需要執行緒同步。一個非常基本的同步原語是將sum宣告為volatile。在存取變數之前,執行緒將重新整理其本地暫存器並從記憶體中獲取值。存取後,該值立即寫入記憶體。

以下是JIT編譯器完成的一些常規優化 -

  • 方法內聯
  • 死程式碼消除
  • 用於優化呼叫站點的啟發式演算法
  • 不斷摺疊