1. 程式人生 > >【深入Java虛擬機器】之七:Javac編譯與JIT編譯

【深入Java虛擬機器】之七:Javac編譯與JIT編譯

編譯過程

    不論是物理機還是虛擬機器,大部分的程式程式碼從開始編譯到最終轉化成物理機的目的碼或虛擬機器能執行的指令集之前,都會按照如下圖所示的各個步驟進行:


    其中綠色的模組可以選擇性實現。很容易看出,上圖中間的那條分支是解釋執行的過程(即一條位元組碼一條位元組碼地解釋執行,如JavaScript),而下面的那條分支就是傳統編譯原理中從原始碼到目標機器程式碼的生成過程。

    如今,基於物理機、虛擬機器等的語言,大多都遵循這種基於現代經典編譯原理的思路,在執行前先對程式原始碼進行詞法解析和語法解析處理,把原始碼轉化為抽象語法樹。對於一門具體語言的實現來說,詞法和語法分析乃至後面的優化器和目的碼生成器都可以選擇獨立於執行引擎,形成一個完整意義的編譯器去實現,這類代表是C/C++語言。也可以把抽象語法樹或指令流之前的步驟實現一個半獨立的編譯器,這類代表是Java語言。又或者可以把這些步驟和執行引擎全部集中在一起實現,如大多數的JavaScript執行器。

Javac編譯

在Java中提到“編譯”,自然很容易想到Javac編譯器將*.java檔案編譯成為*.class檔案的過程,這裡的Javac編譯器稱為前端編譯器,其他的前端編譯器還有諸如Eclipse JDT中的增量式編譯器ECJ等。相對應的還有後端編譯器它在程式執行期間將位元組碼轉變成機器碼(現在的Java程式在執行時基本都是解釋執行加編譯執行),如HotSpot虛擬機器自帶的JIT(Just In Time Compiler)編譯器(分Client端和Server端)另外,有時候還有可能會碰到靜態提前編譯器(AOT,Ahead Of Time Compiler)直接把*.java檔案編譯成本地機器程式碼,如GCJ、Excelsior JET等,這類編譯器我們應該比較少遇到。

    下面簡要說下Javac編譯(前端編譯)的過程。

    詞法、語法分析

    詞法分析是將原始碼的字元流轉變為標記(Token)集合。單個字元是程式編寫過程中的的最小元素,而標記則是編譯過程的最小元素,關鍵字、變數名、字面量、運算子等都可以成為標記,比如整型標誌int由三個字元構成,但是它只是一個標記,不可拆分。

    語法分析是根據Token序列來構造抽象語法樹的過程。抽象語法樹是一種用來描述程式程式碼語法結構的樹形表示方式,語法樹的每一個節點都代表著程式程式碼中的一個語法結構,如bao、型別、修飾符、運算子等。經過這個步驟後,編譯器就基本不會再對原始碼檔案進行操作了,後續的操作都建立在抽象語法樹之上。

    填充符號表

    完成了語法分析和詞法分析之後,下一步就是填充符號表的過程。符號表是由一組符號地址和符號資訊構成的表格。符號表中所登記的資訊在編譯的不同階段都要用到,在語義分析(後面的步驟)中,符號表所登記的內容將用於語義檢查和產生中間程式碼,在目的碼生成階段,黨對符號名進行地址分配時,符號表是地址分配的依據。

    語義分析

     語法樹能表示一個結構正確的源程式的抽象,但無法保證源程式是符合邏輯的。而語義分析的主要任務是讀結構上正確的源程式進行上下文有關性質的審查。語義分析過程分為標註檢查和資料及控制流分析兩個步驟:

  • 標註檢查步驟檢查的內容包括諸如變數使用前是否已被宣告、變數和賦值之間的資料型別是否匹配等。
  • 資料及控制流分析是對程式上下文邏輯更進一步的驗證,它可以檢查出諸如程式區域性變數在使用前是否有賦值、方法的每條路徑是否都有返回值、是否所有的受查異常都被正確處理了等問題。

    位元組碼生成

    位元組碼生成是Javac編譯過程的最後一個階段。位元組碼生成階段不僅僅是把前面各個步驟所生成的資訊轉化成位元組碼寫到磁碟中,編譯器還進行了少量的程式碼新增和轉換工作。 例項構造器<init>()方法和類構造器<clinit>()方法就是在這個階段新增到語法樹之中的(這裡的例項構造器並不是指預設的建構函式,而是指我們自己過載的建構函式,如果使用者程式碼中沒有提供任何建構函式,那編譯器會自動新增一個沒有引數、訪問許可權與當前類一致的預設建構函式,這個工作在填充符號表階段就已經完成了)。


JIT編譯

    Java程式最初是僅僅通過直譯器解釋執行的,即對位元組碼逐條解釋執行,這種方式的執行速度相對會比較慢,尤其當某個方法或程式碼塊執行的特別頻繁時,這種方式的執行效率就顯得很低。於是後來在虛擬機器中引入了JIT編譯器(即時編譯器)當虛擬機發現某個方法或程式碼塊執行特別頻繁時,就會把這些程式碼認定為“Hot Spot Code”(熱點程式碼),為了提高熱點程式碼的執行效率,在執行時,虛擬機器將會把這些程式碼編譯成與本地平臺相關的機器碼,並進行各層次的優化,完成這項任務的正是JIT編譯器。

    現在主流的商用虛擬機器(如Sun HotSpot、IBM J9)中幾乎都同時包含直譯器和編譯器(三大商用虛擬機器之一的JRockit是個例外,它內部沒有直譯器,因此會有啟動相應時間長之類的缺點,但它主要是面向服務端的應用,這類應用一般不會重點關注啟動時間)。二者各有優勢:當程式需要迅速啟動和執行時,直譯器可以首先發揮作用,省去編譯的時間,立即執行;當程式執行後,隨著時間的推移,編譯器逐漸會返回作用,把越來越多的程式碼編譯成原生代碼後,可以獲取更高的執行效率。解釋執行可以節約記憶體,而編譯執行可以提升效率。

    HotSpot虛擬機器中內建了兩個JIT編譯器:Client Complier和Server Complier,分別用在客戶端和服務端,目前主流的HotSpot虛擬機器中預設是採用直譯器與其中一個編譯器直接配合的方式工作。

執行過程中會被即時編譯器編譯的“熱點程式碼”有兩類:

  • 被多次呼叫的方法。
  • 被多次呼叫的迴圈體。

  兩種情況,編譯器都是以整個方法作為編譯物件,這種編譯也是虛擬機器中標準的編譯方式。要知道一段程式碼或方法是不是熱點程式碼,是不是需要觸發即時編譯,需要進行Hot Spot Detection(熱點探測)。目前主要的熱點 判定方式有以下兩種:

  • 基於取樣的熱點探測:採用這種方法的虛擬機器會週期性地檢查各個執行緒的棧頂,如果發現某些方法經常出現在棧頂,那這段方法程式碼就是“熱點程式碼”。這種探測方法的好處是實現簡單高效,還可以很容易地獲取方法呼叫關係,缺點是很難精確地確認一個方法的熱度,容易因為受到執行緒阻塞或別的外界因素的影響而擾亂熱點探測。
  • 基於計數器的熱點探測:採用這種方法的虛擬機器會為每個方法,甚至是程式碼塊建立計數器,統計方法的執行次數,如果執行次數超過一定的閥值,就認為它是“熱點方法”。這種統計方法實現複雜一些,需要為每個方法建立並維護計數器,而且不能直接獲取到方法的呼叫關係,但是它的統計結果相對更加精確嚴謹。

    在HotSpot虛擬機器中使用的是第二種——基於計數器的熱點探測方法,因此它為每個方法準備了兩個計數器:方法呼叫計數器和回邊計數器。

    方法呼叫計數器用來統計方法呼叫的次數,在預設設定下,方法呼叫計數器統計的並不是方法被呼叫的絕對次數,而是一個相對的執行頻率,即一段時間內方法被呼叫的次數。

    回邊計數器用於統計一個方法中迴圈體程式碼執行的次數(準確地說,應該是回邊的次數,因為並非所有的迴圈都是回邊),在位元組碼中遇到控制流向後跳轉的指令就稱為“回邊”。

    在確定虛擬機器執行引數的前提下,這兩個計數器都有一個確定的閥值,當計數器的值超過了閥值,就會觸發JIT編譯。觸發了JIT編譯後,在預設設定下,執行引擎並不會同步等待編譯請求完成,而是繼續進入直譯器按照解釋方式執行位元組碼,直到提交的請求被編譯器編譯完成為止(編譯工作在後臺執行緒中進行)。當編譯工作完成後,下一次呼叫該方法或程式碼時,就會使用已編譯的版本。

    由於方法計數器觸發即時編譯的過程與回邊計數器觸發即時編譯的過程類似,因此這裡僅給出方法呼叫計數器觸發即時編譯的流程:


Javac位元組碼編譯器與虛擬機器內的JIT編譯器的執行過程合起來其實就等同於一個傳統的編譯器所執行的編譯過程。

相關推薦

深入Java虛擬機器Javac編譯JIT編譯

編譯過程     不論是物理機還是虛擬機器,大部分的程式程式碼從開始編譯到最終轉化成物理機的目的碼或虛擬機器能執行的指令集之前,都會按照如下圖所示的各個步驟進行:     其中綠色的模組可

深入Java虛擬機器深入JVM位元組碼執行引擎

我們都知道,在當前的Java中(1.0)之後,編譯器講原始碼轉成位元組碼,那麼位元組碼如何被執行的呢?這就涉及到了JVM的位元組碼執行引擎,執行引擎負責具體的程式碼呼叫及執行過程。就目前而言,所有的執行引擎的基本一致: 輸入:位元組碼檔案 處理:位元組碼解析 輸出:執

深入Java虛擬Javac編譯JIT編譯

p s ots 基本 關鍵字 目前 關註 script 和數 語言 轉載請註明出處:http://blog.csdn.net/ns_code/article/details/18009455 編譯過程 不論是物理機還是虛擬機,大部分的程序代碼從開始編譯到最終轉化

深入Java虛擬機器類載入及執行子系統的案例實戰

摘自《深入理解 Java 虛擬機器:JVM 高階特性與最佳實踐》(第二版) 概述         在 Class 檔案格式與執行引擎這部分中,使用者的程式能直接影響的內容並不太多,Class 檔案以何種格式儲存,型

深入Java虛擬機器GC收集器以及JDK7,JDK8中JVM記憶體變化

Java與C++之間有一堵由記憶體動態分配和垃圾收集技術所圍成的“高牆”,牆外面的人想進去,牆裡面的人卻想出來。 GC收集器 如果說收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。 Java虛擬機器規範中對垃圾收集器應該如何實現並沒有任何規定,因此不同的廠

深入Java虛擬機器類載入機制

類載入過程     類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用和解除安裝七個階段。它們開始的順序如下圖所示:     其中類載入的過程包括了載入、驗證、準備、

深入Java虛擬機器Java垃圾收集機制

物件引用     Java中的垃圾回收一般是在Java堆中進行,因為堆中幾乎存放了Java中所有的物件例項。談到Java堆中的垃圾回收,自然要談到引用。在JDK1.2之前,Java中的引用定義很很純粹:如果reference型別的資料中儲存的數值代表的是另外一塊

深入Java虛擬機器類初始化

    類初始化是類載入過程的最後一個階段,到初始化階段,才真正開始執行類中的Java程式程式碼。虛擬機器規範嚴格規定了有且只有四種情況必須立即對類進行初始化: 遇到new、getstatic、putstatic、invokestatic這四條位元組碼指令時,

深入Java虛擬機器類載入機制

    我們來看得到該結果的步驟。首先在準備階段為類變數分配記憶體並設定類變數初始值,這樣A和B均被賦值為預設值0,而後再在呼叫<clinit>()方法時給他們賦予程式中指定的值。當我們呼叫Child.b時,觸發Child的<clinit>()方法,根據規則2,在此之前,要先執行完其父

深入Java虛擬機器Class類檔案結構

平臺無關性     Java是與平臺無關的語言,這得益於Java原始碼編譯後生成的儲存位元組碼的檔案,即Class檔案,以及Java虛擬機器的實現。不僅使用Java編譯器可以把Java程式碼編譯成儲存位元組碼的Class檔案,使用JRuby等其他語言的編譯器也可以把程式

深入Java虛擬機器十一全面理解Java記憶體模型(JMM)及volatile關鍵字

轉自:https://blog.csdn.net/javazejian/article/details/72772461 關聯文章: 深入理解Java型別資訊(Class物件)與反射機制 深入理解Java列舉型別(enum) 深入理解Java註解型別(@Annotation)

深入Java虛擬機器記憶體區域詳解(Eden Space、Survivor Space、Old Gen、Code Cache和Perm Gen)

1.記憶體區域劃分 限定商用虛擬機器基本都採用分代收集演算法進行垃圾回收。根據物件的生命週期的不同將記憶體劃分為幾塊,然後根據各塊的特點採用最適當的收集演算法。大批物件死去、少量物件存活的,使用複製演算法,複製成本低;物件存活率高、沒有額外空間進行分配擔保的,採用標記-清除演算法

深入Java虛擬機器十早期(編譯期)優化

轉自:https://blog.csdn.net/tjiyu/article/details/53786262 Java編譯(二)Java前端編譯: Java原始碼編譯成Class檔案的過程     &n

深入Java虛擬機器八動態型別語言支援

編譯型語言和解釋型語言 1、編譯型語言 需通過編譯器(compiler)將原始碼編譯成機器碼,之後才能執行的語言。一般需經過編譯(compile)、連結(linker)這兩個步驟。 編譯是把原始碼編譯成機器碼, 連結是把各個模組的機器碼和依賴庫串連起來生成可執行檔案。 優

深入Java虛擬機器精華總結(面試)

一.執行時資料區域   Java虛擬機器管理的記憶體包括幾個執行時資料記憶體:方法區、虛擬機器棧、堆、本地方法棧、程式計數器,其中方法區和堆是由執行緒共享的資料區,其他幾個是執行緒隔離的資料區。   1.1程式計數器   程式計數器是一塊較小的記憶體,他可以看做是當前執行緒所執行的行號指示器

深入Java虛擬機器Java虛擬機器工作原理詳解

轉自:https://blog.csdn.net/bingduanlbd/article/details/8363734 一、類載入器 首先來看一下java程式的執行過程。               &nbs

深入Java虛擬Java垃圾收集機制

狀態 nio 得到 man tsp ngs fin 純粹 概念 轉載請註明出處:http://blog.csdn.net/ns_code/article/details/18076173 對象引用 Java中的垃圾回收一般是在Java堆中進行,因為堆中幾乎存

深入Java虛擬Class類文件結構

本質 拒絕 處理 implement align 默認值 改變 占用 至少 平臺無關性 Java是與平臺無關的語言,這得益於Java源代碼編譯後生成的存儲字節碼的文件,即Class文件,以及Java虛擬機的實現。不僅使用Java編譯器可以把Java代碼編譯成存儲字節

深入Java虛擬機器之一Java記憶體區域記憶體溢位

記憶體區域     Java虛擬機器在執行Java程式的過程中會把他所管理的記憶體劃分為若干個不同的資料區域。Java虛擬機器規範將JVM所管理的記憶體分為以下幾個執行時資料區:程式計數器、Java虛擬機器棧、本地方法棧、Java堆、方法區。下面詳細闡述各資料區所儲存的資料型

深入Java虛擬機器(5)多型性實現機制—靜態分派動態分派

方法解析 Class檔案的編譯過程中不包含傳統編譯中的連線步驟,一切方法呼叫在Class檔案裡面儲存的都只是符號引用,而不是方法在實際執行時記憶體佈局中的入口地址。這個特性給Java帶來了更強大的動態擴充套件能力,使得可以在類執行期間才能確定某些目標方法的直接引