深入理解JVM - JVM編譯器

編譯器類型

編譯器最終的目的是將我們寫的源代碼編譯成機器能識別的機器碼。 在JVM 中有三個非常重要的編譯器,它們分別是:前端編譯器、JIT 編譯器和AOT編譯器。

前端編譯器

將源代碼轉化成字節碼,如javac;我們一般稱 javac 編譯器爲前端編譯器,因爲其發生在整個編譯的前期。javac編譯過程大致可以分爲1個準備過程和3個處理過程,它們分別如下所示。

  1. 準備過程:初始化插入式註解處理器,java是支持註解的。
  2. 解析與填充符號表過程,包括:
    a. 詞法、語法分析。將源代碼的字符流轉變爲標記集合,構造出抽象語法樹。
    b. 填充符號表。產生符號地址和符號信息。
  3. 插入式註解處理器的註解處理過程:插入式註解處理器的執行階段,本章的實戰部分會設計一個插入式註解處理器來影響Javac的編譯行爲。
  4. 分析與字節碼生成過程,包括:
    a. 標註檢查。對語法的靜態信息進行檢查。
    b. 數據流及控制流分析。對程序動態運行過程進行檢查。
    c. 解語法糖。將簡化代碼編寫的語法糖還原爲原有的形式。
    d. 字節碼生成。將前面各個步驟所生成的信息轉化成字節碼。

JIT編譯器(即時編譯器)

將字節碼轉換成機器碼,如HotSpot VM的C1和C2編譯器。

當源代碼轉化爲字節碼之後,程序要運行程序,有兩種選擇:一種是使用 Java 解釋器解釋執行字節碼,另一種則是使用編譯器將字節碼轉化爲本地機器代碼。

  • 解釋器執行:將編譯好的字節碼一行一行地翻譯爲機器碼執行,程序啓動速度快,但是運行速度慢;
  • 編譯器執行:以方法爲單位,將字節碼一次性翻譯爲機器碼後執行,程序啓動速度慢,但是運行速度快。

在整個Java虛擬機執行架構裏,解釋器與編譯器經常是相輔相成地配合工作,當虛擬機發行某個方法執行特別頻繁時,就會把這些代碼判定成“熱點代碼”,爲了提高熱點代碼的執行效率,在運行時,虛擬機將會把這些代碼編譯成本地機器碼,並以各種手段儘可能地進行代碼優化,運行時完成這個任務的後端編譯器被稱爲即時編譯器。當執行JIT編譯後的機器碼出現問題時,那麼虛擬機又會使用解釋器來執行代碼。

在 HotSpot 虛擬機內置了多個即時編譯器,分別稱爲 Client 編譯器(C1)、Server 編譯器(C2)和graal編譯器。

  • C1:即Client編譯器,面向對啓動性能有要求的客戶端GUI程序,將字節碼轉換爲機器碼時只進行簡單、可靠的優化,因此編譯的時間較短,通過-client參數打開。
  • C2:即Server編譯器,面向對性能峯值有要求的服務端程序,將字節碼轉換爲機器碼時會進行激進、複雜的優化,因此編譯時間長,但是在運行過程中性能更好,通過-server參數打開。
  • graal:graal是JDK10引入的一個編譯器,目的是用來替換C2編譯器。

虛擬機執行模式

虛擬機有三種執行模式:混合模式(Mixed Mode)、解釋模式(Interpreted Mode)和編譯模式(CompiledMode),默認是混合模式。

  • 混合模式(Mixed Mode):解釋器與編譯器搭配使用的方式在虛擬機中被稱爲“混合模式”(Mixed Mode)。
  • -Xint:指定虛擬機使用“解釋模式”(Interpreted Mode),代碼都使用解釋方式執行;
  • -Xcomp:指定虛擬機使用“編譯模式”,代碼優先採用編譯方式執行程序,但是當編譯後的代碼無法正常執行時,這時使用解釋執行來兜底;

編譯器優化

爲了在程序啓動響應速度與運行效率之間達到最佳平衡,HotSpot虛擬機在編譯子系統中加入了分層編譯的功能,分層編譯根據編譯器編譯、優化的規模與耗時,劃分出不同的編譯層次,其中包括:

  • 第0層:程序只使用解釋執行,並且解釋器不開啓性能監控功能(Profiling)。
  • 第1層:使用客戶端編譯器將字節碼編譯爲本地代碼來運行,進行簡單可靠的穩定優化,不開啓性能監控功能。
  • 第2層:仍然使用客戶端編譯器執行,僅開啓方法及回邊次數統計等有限的性能監控功能。
  • 第3層:仍然使用客戶端編譯器執行,開啓全部性能監控,除了第2層的統計信息外,還會收集如分支跳轉、虛方法調用版本等全部的統計信息。
  • 第4層:使用服務端編譯器將字節碼編譯爲本地代碼,相比起客戶端編譯器,服務端編譯器會啓用更多編譯耗時更長的優化,還會根據性能監控信息進行一些不可靠的激進優化。

以上層次並不是固定不變的,根據不同的運行參數和版本,虛擬機可以調整分層的數量。分層編譯的交互關係,如下:

image.png

-XX:+TieredCompilation,開啓分層編譯,可以讓jvm在啓動時啓用client編譯,隨着代碼變熱後再轉爲server編譯。

熱點探測

對於程序來說,通常只有一部分代碼被經常執行,這些關鍵代碼被稱爲應用的熱點,執行的越多就認爲是越熱,將這些代碼編譯爲本地機器特定的二進制碼,可以有效提高應用性能。目前主流的熱點探測判定方式有兩種,分別是:

  • 基於採樣的熱點探測(Sample Based Hot Spot Code Detection)。採用這種方法的虛擬機會週期性地檢查各個線程的調用棧頂,如果發現某個(或某些)方法經常出現在棧頂,那這個方法就是“熱點方法”。基於採樣的熱點探測的好處是實現簡單高效,還可以很容易地獲取方法調用關係(將調用堆棧展開即可),缺點是很難精確地確認一個方法的熱度,容易因爲受到線程阻塞或別的外界因素的影響而擾亂熱點探測。
  • 基於計數器的熱點探測(Counter Based Hot Spot Code Detection)。採用這種方法的虛擬機會爲每個方法(甚至是代碼塊)建立計數器,統計方法的執行次數,如果執行次數超過一定的閾值就認爲它是“熱點方法”。這種統計方法實現起來要麻煩一些,需要爲每個方法建立並維護計數器,而且不能直接獲取到方法的調用關係。但是它的統計結果相對來說更加精確嚴謹。

AOT編譯器

將源代碼直接編譯成機器碼,稱爲提前編譯器(AOT編譯器),如:Jaotc;

提前編譯器主要的優點:

  • 提前編譯器不會佔用程序的運行時間和運算資源,但是及時編譯器會,這也是即時編譯器的最大弱點。
  • 可以作爲即時編譯器的緩存,改善程序的啓動時間。

提前編譯器主要的缺點,也是即時編譯器的優點:

  • 不能進行性能分析制導優化(Profile-Guided Optimization,PGO),因爲提前編譯不能收集到程序的運行時監控數據,無法定位熱點代碼,所以不能集中優化和分配更好的資源進行制導優化。
  • 不能進行激進預測性優化,因爲提前編譯器編器必須保證優化後的程序一定能正常運行,所以不能進行一些激進優化。
  • 不能進行鏈接時優化(Link-Time Optimization,LTO),因爲主程序與動態鏈接庫的代碼在它們編譯時是完全獨立的,兩者各自編譯、優化自己的代碼,所以提前編譯器無法進行鏈接時優化,但是程序運行時會將所有的Class加載到虛擬機中,所以即時編譯器可以進行鏈接時優化

總結

從應用啓動速度來看:解釋執行 > AOT 編譯器 > JIT 編譯器。
從應用運行速度來看: JIT 編譯器> AOT 編譯器 > 解釋執行。

JVM虛擬爲了保持程序啓動時間和運行效率的平衡,一般會使用多種方式配合工作。

參考

《深入理解JAVA虛擬機》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章