你瞭解JVM中的 JIT 即時編譯及優化技術嗎?

JVM Client 模式和 Server模式的區別


經過 java -version 可查看 JVM 所處的模式,並能夠經過修改配置文件進行配置,那它們有什麼區別呢?java

Server:-Server 模式啓動時,速度較慢,可是啓動以後,性能更高,適合運行服務器後臺程序程序員

Client:-Client 模式啓動時,速度較快,啓動以後不如 Server,適合用於桌面等有界面的程序數組

熱點代碼

理解服務器

當虛擬機發現某個方法或代碼塊的運行特別頻繁時,就會把這些代碼認定爲「熱點代碼」。架構

熱點代碼的分類性能

  • 被屢次調用的方法
一個方法被調用得多了,方法體內代碼執行的次數天然就多,成爲「熱點代碼」是理所固然的。
  • 被屢次執行的循環體

一個方法只被調用過一次或少許的幾回,可是方法體內部存在循環次數較多的循環體,這樣循環體的代碼也被重複執行屢次,所以這些代碼也應該認爲是「熱點代碼」。優化

上面提到的屢次是一個不具體的詞語,那究竟是多少次才能成爲熱點代碼呢?操作系統

如何檢測熱點代碼線程

判斷一段代碼是不是熱點代碼,是否須要觸發即便編譯,這樣的行爲稱爲熱點探測,熱點探測並不必定知道方法具體被調用了多少次,目前主要的熱點探測斷定方式有兩種:翻譯

  • 基於採樣的熱點探測:採用這種方法的虛擬機會週期性地檢查各個線程的棧頂若是發現某個(或某些)方法常常出如今棧頂,那這個方法就是「熱點方法」
優勢:實現簡單高效,容易獲取方法調用關係(將調用堆棧展開便可)

缺點:不精確,容易由於由於受到線程阻塞或別的外界因素的影響而擾亂熱點探測

  • 基於計數器的熱點探測:採用這種方法的虛擬機會爲每一個方法(甚至是代碼塊)創建計數器,統計方法的執行次數,若是次數超過必定的閾值就認爲它是「熱點方法」
優勢:統計結果精確嚴謹

缺點:實現麻煩,須要爲每一個方法創建並維護計數器,不能直接獲取到方法的調用關係

HotSpot使用第二種 - 基於計數器的熱點探測方法。

肯定了檢測熱點代碼的方式,如何計算具體的次數呢?

計數器的種類(兩種共同協做)

  • 方法調用計數器:這個計數器用於統計方法被調用的次數。默認閾值在 Client 模式下是 1500 次,在 Server 模式下是 10000 次
  • 回邊計數器:統計一個方法中循環體代碼執行的次數
瞭解了熱點代碼和計數器有什麼用呢?達到計數器的閾值會觸發後文講解的即時編譯,也就是說即時編譯是須要達到某種條件纔會觸發的,先寫結論,後文講解什麼是即時編譯器。

兩個計數器的協做(這裏討論的是方法調用計數器的狀況):當一個方法被調用時,會先檢查該方法是否存在被 JIT(後文講解) 編譯過的版本,若是存在,則優先使用編譯後的本地代碼來執行。若是不存在已被編譯過的版本,則將此方法的調用計數器加 1,而後判斷方法調用計數器與回邊計數器之和是否超過方法調用計數器的閾值。若是已經超過閾值,那麼將會向即時編譯器提交一個該方法的代碼編譯請求。

當編譯工做完成以後,這個方法的調用入口地址就會被系統自動改爲新的,下一次調用該方法時就會使用已編譯的版本。

什麼是字節碼、機器碼、本地代碼?

字節碼是指日常所瞭解的 .class 文件,Java 代碼經過 javac 命令編譯成字節碼

機器碼和本地代碼都是指機器能夠直接識別運行的代碼,也就是機器指令

字節碼是不能直接運行的,須要通過 JVM 解釋或編譯成機器碼才能運行

此時你要問了,爲何 Java 不直接編譯成機器碼,這樣不是更快嗎?

1. 機器碼是與平臺相關的,也就是操做系統相關,不一樣操做系統能識別的機器碼不一樣,若是編譯成機器碼那豈不是和 C、C++差很少了,不能跨平臺,Java 就沒有那響亮的口號 「一次編譯,處處運行」;

2.之因此不一次性所有編譯,是由於有一些代碼只運行一次,不必編譯,直接解釋運行就能夠。而那些「熱點」代碼,反覆解釋執行確定很慢,JVM 在運行程序的過程當中不斷優化,用JIT編譯器編譯那些熱點代碼,讓他們不用每次都逐句解釋執行;

3.還有一方面的緣由是後文講解的解釋器與編譯器共存的緣由。

什麼是 JIT ?

爲了提升熱點代碼的執行效率,在運行時,虛擬機將會把這些代碼編譯成與本地平臺相關的機器碼,並進行各類層次的優化,完成這個任務的編譯器稱爲即時編譯器(Just In Time Compiler),簡稱 JIT 編譯器

什麼是編譯和解釋?

編譯器:把源程序的每一條語句都編譯成機器語言,並保存成二進制文件,這樣運行時計算機能夠直接以機器語言來運行此程序,速度很快;

解釋器:只在執行程序時,才一條一條的解釋成機器語言給計算機來執行,因此運行速度是不如編譯後的程序運行的快的;

經過javac命令將 Java 程序的源代碼編譯成 Java 字節碼,即咱們常說的 class 文件。這是咱們一般意義上理解的編譯。

字節碼並非機器語言,要想讓機器可以執行,還須要把字節碼翻譯成機器指令。這個過程是Java 虛擬機作的,這個過程也叫編譯。是更深層次的編譯。(實際上就是解釋,引入 JIT 以後也存在編譯)

此時又有疑惑了,Java 不是解釋執行的嗎?

沒錯,Java 須要將字節碼逐條翻譯成對應的機器指令而且執行,這就是傳統的 JVM 的解釋器的功能,正是因爲解釋器逐條翻譯並執行這個過程的效率低,引入了 JIT 即時編譯技術。

必須指出的是,無論是解釋執行,仍是編譯執行,最終執行的代碼單元都是可直接在真實機器上運行的機器碼,或稱爲本地代碼

附一張圖來理解


編譯原理參考:[深刻分析Java的編譯原理](www.hollischuang.com/archives/23…)

爲什麼 HotSpot 虛擬機要使用解釋器與編譯器並存的架構?

解釋器與編譯器二者各有優點

解釋器:當程序須要迅速啓動和執行的時候,解釋器能夠首先發揮做用,省去編譯的時間,當即執行。

編譯器:在程序運行後,隨着時間的推移,編譯器逐漸發揮做用,把愈來愈多的代碼編譯成本地代碼以後,能夠獲取更高的執行效率。

二者的協做:在程序運行環境中內存資源限制較大時,可使用解釋執行節約內存,反之可使用編譯執行來提高效率。當經過編譯器優化時,發現並無起到優化做用,,能夠經過逆優化退回到解釋狀態繼續執行。

即時編譯器與 Java 虛擬機的關係

即時編譯器並非虛擬機必需的部分,Java 虛擬機規範並無規定 Java 虛擬機內必需要有即時編譯器的存在,更沒有限定或指導即時編譯器應該如何去實現。

可是,即時編譯器編譯性能的好壞、代碼優化程度的高低倒是衡量一款商用虛擬機優秀與否的最關鍵的指標之一。它也是虛擬機中最核心且最能體現虛擬機技術水平的部分。

即時編譯器的分類

  • Client Compiler - C1編譯器
  • Server Compiler - C2編譯器
目前主流的 HotSpot 虛擬機(JDK1.7 及以前版本的虛擬機)默認採用一個解釋器和其中一個編譯器直接配合的方式工做,程序使用哪一個編譯器,取決於虛擬機運行的模式,就是文章開頭提到的兩種模式。


在 HotSpot 中,解釋器和 JIT 即時編譯器是同時存在的,他們是 JVM 的兩個組件。對於不一樣類型的應用程序,用戶能夠根據自身的特色和需求,靈活選擇是基於解釋器運行仍是基於 JIT 編譯器運行。HotSpot 爲用戶提供了幾種運行模式供選擇,可經過參數設定,分別爲:解釋模式、編譯模式、混合模式,HotSpot 默認是混合模式,須要注意的是編譯模式並非徹底經過 JIT 進行編譯,只是優先採用編譯方式執行程序,可是解釋器仍然要在編譯沒法進行的狀況下介入執行過程。

分層編譯

產生的緣由:因爲即時編譯器編譯本地代碼須要佔用程序運行時間,要編譯出優化程度更高的代碼,所花費的時間可能更長;並且要想編譯出優化程度更高的代碼,解釋器可能還要替編譯器收集性能監控信息,這對解釋執行的速度也有影響。爲了在程序啓動響應速度與運行效率之間達到最佳平衡,HotSpot 虛擬機啓用分層編譯的策略

分層編譯根據編譯器編譯、優化的規模與耗時,劃分出不一樣的編譯層次:

  • 第 0 層:程序解釋執行,解釋器不開啓性能監控功能,可觸發第 1 層編譯。
  • 第 1 層:也稱爲 C1 編譯,將字節碼編譯爲本地代碼,進行簡單,可靠的優化,若有必要將加入性能監控的邏輯。
  • 第 2 層(或 2 層以上):也稱爲 C2 編譯,也是將字節碼編譯爲本地代碼,可是會啓用一些編譯耗時較長的優化,甚至會根據性能監控信息進行一些不可靠的激進優化。
實施分層編譯後,Client Compiler 和 Server Compiler 將會同時工做,許多代碼均可能會被屢次編譯看,用 Client Compiler 獲取更高的編譯速度,用 Server Compiler 獲取更好的編譯質量,在解釋執行的時候也無須再承擔收集性能監控信息的任務。


編譯優化技術

Java 程序員有一個共識,以編譯方式執行本地代碼比解釋執行方式更快,之因此有這樣的共識,除去虛擬機解釋執行字節碼時額外消耗時間的緣由外,還有一個重要的緣由就是虛擬機設計團隊幾乎把對代碼的全部優化措施都集中在了即時編譯器中,所以通常來講,即時編譯器產生的本地代碼會比 javac 產生的字節碼更優秀。如下是具備表明性的 HotSpot 虛擬機的即時編譯器在生成代碼時採用的代碼優化技術:

  • 語言無關的經典優化技術之一:公共子表達式消除
若是一個表達式 E 已經計算過了,而且從先前的計算到如今 E 中全部變量的值都沒有發生變化,那麼 E 的此次出現就成爲了公共子表達式。對於這種表達式,不必花時間再對它進行計算,只須要直接使用前面計算過的表達式結果代替 E 就能夠了。例子:int d = (c*b) * 12 + a + (a+ b * c) -> int d = E * 12 + a + (a+ E)

  • 語言相關的經典優化技術之一:數組範圍檢查消除
在 Java 語言中訪問數組元素的時候系統將會自動進行上下界的範圍檢查,超出邊界會拋出異常。對於虛擬機的執行子系統來講,每次數組元素的讀寫都帶有一次隱含的條件斷定操做,對於擁有大量數組訪問的程序代碼,這無疑是一種性能負擔。Java 在編譯期根據數據流分析能夠斷定範圍進而消除上下界檢查,節省屢次的條件判斷操做。
  • 最重要的優化技術之一:方法內聯
簡單的理解爲把目標方法的代碼「複製」到發起調用的方法中,消除一些無用的代碼。只是實際的 JVM 中的內聯過程很複雜,在此不分析。
  • 最前沿的優化技術之一:逃逸分析
逃逸分析的基本行爲就是分析對象動態做用域:當一個對象在方法中杯定義後,它可能被外部方法所引用,例如做爲調用參數傳遞到其餘方法中,稱爲 方法逃逸。甚至可能被外部線程訪問到,譬如賦值給類變量或能夠在其餘線程中訪問的實例變量,稱爲 線程逃逸。

若是能證實一個對象不會逃逸到方法或線程以外,也就是別的方法或線程沒法經過任何途徑訪問到這個對象,則能夠爲這個變量進行一些高效的優化:

  • 棧上分配:將不會逃逸的局部對象分配到棧上,那對象就會隨着方法的結束而自動銷燬,減小垃圾收集系統的壓力。
  • 同步消除:若是該變量不會發生線程逃逸,也就是沒法被其餘線程訪問,那麼對這個變量的讀寫就不存在競爭,能夠將同步措施消除掉(同步是須要付出代價的)
  • 標量替換:標量是指沒法在分解的數據類型,好比原始數據類型以及reference類型。而聚合量就是可繼續分解的,好比 Java 中的對象。標量替換若是一個對象不會被外部訪問,而且對象能夠被拆散的話,真正執行時可能不建立這個對象,而是直接建立它的若干個被這個方法使用到的成員變量來代替。這種方式不只可讓對象的成員變量在棧上分配和讀寫,還能夠爲後後續進一步的優化手段建立條件。


按本身理解整理的,知識點順序不知是否合適,還請你們指導。


參考來源:

《深刻理解 Java 虛擬機》

www.hollischuang.com/archives/23…

相關文章
相關標籤/搜索