Refresh your Java skills--Java中的即時編譯(Just-in-time compilation)

因本身在寫的關於Java9的新書由於篇幅和讀者層次的緣由並不能將能想到的東西都寫進去,故接下來整理出一系列的博文來補充拓展。html

像其餘一些編程語言同樣,Java一般也被稱爲「編譯語言」。但有時你可能會感到困惑,尤爲是當有人告訴你Java是JIT編譯,並問你其中的一些小細節時。java

本文就來講一說JIT編譯的概念。在第一部分,咱們將對不一樣類型的編譯描述一番。第二部分來講說JIT編譯。接下來,咱們將深刻一下JIT編譯在Java中比較特別的地方。
編程

編譯類型

在討論編譯類型以前,咱們須要瞭解什麼是編譯。這是一個將編程語言翻譯成機器可理解的語言(也稱爲機器代碼)的過程。機器語言由CPU執行的指令組成。這個語言是由0-1構成的,如在wikibooks頁面上的這個片斷所示:緩存

0001 00000111
0100 00001001
0000 00011110複製代碼

即時編譯

一樣,咱們知道,Java的javac指令不會生成機器代碼,而是一些名爲字節碼的東西。而這不只僅是一種語言會這麼作(而這也是不少現代語言所發展的一個方向)。好比ActionScript(由ActionScript Virtual Machine執行)或CIL(由C#使用並在Common Language Runtime上執行)。編程語言

在這裏,在咱們的括號中所說的「執行」,也就是即時編譯完成(即字節碼編譯成目標機器可執行的機器碼)。這種特殊類型的編譯發生在解釋給定字節碼的機器上,如ActionScript虛擬機或Java虛擬機(JVM)。字節碼由他們在運行時( on runtime)編譯成機器碼。函數

這種編譯帶來了一些好處。第一個顯着的優勢是能夠作到根據所運行機器參數來優化編譯的代碼。靜態編譯器爲目標機器進行優化並一次生成機器代碼。另外一方面,JIT編譯器提供了一種中間代碼,它被轉換和優化爲特定於執行機器的機器代碼。關於這裏有一篇解釋的比較通俗的文章動態編譯和靜態編譯及Java執行,有興趣能夠看看優化

第二個優勢是便攜性。轉換爲字節碼的代碼能夠在安裝了虛擬機的任何計算機上運行。spa

Java中的即時編譯

So,Java是即時編譯爲機器代碼的。想要檢查編譯機器代碼,咱們能夠啓用多個JVM參數:.net

  • -XX:+ PrintCompilation翻譯

    經過這個參數,咱們能夠獲得方法編譯結果的輸出。其輸出的樣例:

71 1 java.lang.String :: indexOf(70 bytes)
73 2 sun.nio.cs.UTF_8 $ Encoder :: encode(361 bytes)
87 3 java.lang.String :: hashCode(55 bytes)複製代碼
  • 輸出被格式化爲列,第一列(例如71)是時間戳。第二列返回惟一的編譯器任務ID(1,2,3 ...)。以後咱們能夠看到編譯的方法。在括號中指定了編譯字節碼的字節。咱們能夠看到indexOf方法的大小是70字節,encode 方法是361字節等等。

  • -XX:+ UnlockDiagnosticVMOptions

    一個簡單的標誌,JVM診斷的補充選項。

  • -XX:+ PrintInlining

    經過這個配置,咱們能夠看到編譯方法的細節。內聯是編譯器優化編譯代碼重要的工做方式。請看如下方法:

public void testMethod() {
  callAnotherMethod();
}複製代碼

經過內聯,函數callAnotherMethod()將被callAnotherMethod的內容替換。正由於如此,在運行時,機器不會從一個方法跳轉到另外一個方法,並可以以內聯方式執行代碼。JIT經過此操做用來避免在堆棧上放置參數的複雜狀況。當咱們啓用此參數(+PrintInlining)並運行代碼時,咱們能夠看到相似下面的結果:

75 1 java.lang.String :: indexOf(70 bytes)
77 2 sun.nio.cs.UTF_8 $ Encoder :: encode(361 bytes)
                    @ 66 java.lang.String :: indexOfSupplementary(71 bytes) too big
                    @ 14 java.lang.Math :: min(11 bytes)(intrinsic)
                    @ 139 java.lang.Character :: isSurrogate(18 bytes) never executed
89 3 java.lang.String :: hashCode(55 bytes)複製代碼

讓咱們回到理論層面面,Java中的JIT編譯(這裏說是動態編譯)能夠是(這裏能夠參考一篇文章
JVM即時編譯(JIT),我這裏用更加暴力通俗的方式說了下,能知道是個什麼做用就能夠):

  • lazy:只有真正使用的方法(在運行時調用)纔會被編譯成機器代碼。
  • adaptive(自適應):整個程序被編譯成一些髒機器代碼。此代碼僅針對很是經常使用的方法進行了優化。

已經編譯的字節碼存儲到代碼緩存中。這是一個結構,全部編譯的方法。當再次調用給定方法時,它不會從頭開始編譯,而是從代碼緩存中加載。可是,當編譯器認爲能夠更好地優化此方法時,緩存方法能夠被覆蓋。在優化技術中,咱們能夠經過如下區分:

  • 內聯:在前面的描述中能夠知道,能夠避免方法跳躍。
  • 垃圾代碼(稱之死代碼更恰當):當某些對象存在於字節碼中且不被使用時,編譯器能夠決定從機器代碼中刪除它們。
  • 循環優化:編譯器能夠組織並優化循環執行順序或對尾遞歸優化成for循環等,以此來優化CPU所執行的代碼。
  • 用實現方法替換接口方法:當給定接口的一個方法有且僅由一個對象實現時,編譯器能夠決定直接使用實現的方法,以免在運行時綁定真正實現的方法所引發的開銷。

在本文中,咱們解釋了即時編譯,即特定用於語言的編譯代碼(如Java的字節碼)轉換爲CPU能夠理解的語言(機器代碼)。編譯器不會進行簡單的編譯,由於它也對編譯代碼進行了一些優化。因爲這些優化,機器代碼儘量地適應目標機器,另外,能夠根據blog.csdn.net/opensure/ar… 這篇文章中的兩張圖來更好的理解下上面所說的一些細節。

相關文章
相關標籤/搜索