為什么解釋器和編譯器共存?
首先明確:
當程序啟動后,解釋器可以馬上發(fā)揮作用,省去編譯的時間,立即執(zhí)行。編譯器要想發(fā)揮作用,把代碼編譯成本地代碼,需要一定的執(zhí)行時間。 但編譯為本地代碼后,執(zhí)行效率高。
所以:
盡管JRockit VM中程序的執(zhí)行性能會非常高效,但程序在啟動時必然需要花費更長的時間來進行編譯。對于服務端應用來說,啟動時間并非是關注重點,但對于那些看中啟動時間的應用場景而言,或許就需要采用解釋器與即時編譯器并存的架構來換取一個平衡點。在此模式下,當Java 虛擬器啟動時,解釋器可以首先發(fā)揮作用,而不必等待即時編譯器全部編譯完成后再執(zhí)行,這樣可以省去許多不必要的編譯時間。隨著時間的推移,編譯器發(fā)揮作用,把越來越多的代碼編譯成本地代碼,獲得更高的執(zhí)行效率。
同時,解釋執(zhí)行在編譯器進行激進優(yōu)化不成立的時候,作為編譯器的“逃生門”。
HotSpot JVM的執(zhí)行方式
當虛擬機啟動的時候,解釋器可以首先發(fā)揮作用,而不必等待即時編譯器全部編譯完成后再執(zhí)行,這樣可以省去許多不必要的編譯時間。并且隨著程序運行時間的推移,即時編譯器逐漸發(fā)揮作用,根據(jù)熱點探測功能,將有價值的字節(jié)碼編譯為本地機器指令,以換取更高的程序執(zhí)行效率。
JIT編譯器
概念解釋
- Java語言的“編譯器”其實是一段“不確定”的操作過程,因為它可能是指一個前端編譯器(其實叫“編譯器的前端”更準確一些)把.java文件轉變成.class文件的過程;
- 也可能是指虛擬機的后端運行編譯器(JIT編譯器)把字節(jié)碼轉變?yōu)闄C器碼的過程。
-
還可能是指使用靜態(tài)提前編譯器(AOT編譯器,Ahead Of Time Compiler)直接把.java文件編譯成本地機器代碼的過程。
image.png
熱點代碼及探測方式
當然是否需要啟動JIT編譯器將字節(jié)碼直接編譯為對應平臺的本地機器指令,則需要根據(jù)代碼被調用指向的頻率而定。關于那些需要被編譯為本地代碼的字節(jié)碼,也被稱之為“熱點代碼”,JIT編譯器在運行時會針對那些頻繁被調用的“熱點代碼”做出深度優(yōu)化,將其直接編譯為對應平臺的本地機器指令,以此提升Java程序的執(zhí)行性能。
- 一個被多次調用的方法,或者是一個方法體內部循環(huán)次數(shù)較多的循環(huán)體都可以被稱之為“熱點代碼”,因此都可以通過JIT編譯器編譯為本地機器指令。由于這種編譯方式發(fā)生在方法的執(zhí)行過程中,因此也被稱之為棧上替換,或簡稱為OSR(On Stack Replacement)編譯。
- 一個方法要被調用對少次,或者一個循環(huán)體究竟需要執(zhí)行多少次循環(huán)才可以到達這個標準?必然需要一個明確的閾值,JIT編譯器才會將這些“熱點代碼”編譯為本地機器指令執(zhí)行。這里主要依靠熱點探測功能。
-
目前HotSpot VM采用的熱點探測方式是基于計數(shù)器的熱點探測。
采用計數(shù)器的熱點探測,HotSpot VM將會為每一個方法都建立2個不同類型的計數(shù)器,分別為方法調用計數(shù)器(Invocation Counter)和回邊計數(shù)器(Back Edge Counter)。- 方法調用計數(shù)器用于統(tǒng)計方法的調用次數(shù)
- 回邊計數(shù)器用于統(tǒng)計循環(huán)體執(zhí)行的循環(huán)次數(shù)
方法調用計數(shù)器
- 這個計數(shù)器就用于統(tǒng)計方法被調用的次數(shù),它的默認閾值在Client模式下是1500次,在Server模式下是10000次。超過這個閾值,就會出發(fā)JIT編譯。
- 這個閾值可以通過虛擬機參數(shù)-XX:CompileThreshold來人為設定。
- 當一個方法被調用時,會先檢查該方法是否存在被JIT編譯過的版本,如果存在,則優(yōu)先使用編譯后的本地代碼來執(zhí)行。如果不存在已被編譯過的版本,則將此方法的調用計數(shù)器值加1,然后判斷方法調用計數(shù)器與回邊計數(shù)器值之和是否超過方法調用計數(shù)器的閾值。如果超過閾值,那么將會向即時編譯器提交一個該方法的代碼編譯請求。
方法調用計數(shù)器
熱度衰減 - 如果不做任何設置,方法調用計數(shù)器統(tǒng)計的并不是方法調用的絕對次數(shù),而是一個相對的執(zhí)行頻率,即一段時間之內方法被調用的次數(shù)。當超過一定的時間限度,如果當大的調用次數(shù)仍然不足以讓它提交給即時編譯器,那這個方法的調用計數(shù)器就會被減少一半,這個過程稱為方法調用計數(shù)器熱度的衰減(Counter Decay),而這段時間就稱為此方法統(tǒng)計的半衰周期(Counter Half Life Time)。
- 進行熱度衰減的動作是在虛擬機進行垃圾收集時順便進行的,可以使用虛擬機參數(shù)-XX:-UseCounterDecay來關閉熱度衰減,讓方法計數(shù)器統(tǒng)計方法調用的絕對次數(shù),這樣,只要系統(tǒng)運行時間足夠長,絕大部分方法都會被編譯成本地代碼。
- 另外,可以使用-XX:CounterHalfLifeTime參數(shù)設置半衰周期的時間,單位是秒。
回邊計數(shù)器
它的作用是統(tǒng)計一個方法中循環(huán)體代碼執(zhí)行的次數(shù),在字節(jié)碼中遇到控制流向后跳轉的指令稱為“回邊”(Back Edge)。顯然,建立回邊計數(shù)器統(tǒng)計的目的就是為了出發(fā)OSR編譯。

HotSpot VM設置程序執(zhí)行方式:
- -Xint:完全采用解釋器模式執(zhí)行程序。
- -Xcomp:完全采用即時編譯器模式執(zhí)行程序。如果即時編譯器出現(xiàn)問題,解釋器會介入執(zhí)行。
- -Xmind:采用解釋器+即時編譯器的混合模式共同執(zhí)行程序。
HotSpot VM中的JIT分類
在HotSpot VM中內嵌有兩個JIT編譯器,分別為Client Compiler和Server Compiler,但大多數(shù)情況下我們簡稱C1編譯器和C2編譯器。開發(fā)人員可以通過如下命令顯示指定Java虛擬機在運行時到底使用哪一種即時編譯器。
- -client:指定Java訊虛擬機運行在Client模式下,并使用C1編譯器;
**C1編譯器會對字節(jié)碼進行簡單和可靠的優(yōu)化,耗時短。以達到更快的編譯速度。 -
server:指定Java虛擬機運行在Server模式下,并使用C2編譯器。
**C2進行耗時較長的優(yōu)化,以及激進優(yōu)化。但優(yōu)化的代碼執(zhí)行效率更高。
image.png
總結:
- 一般來講,JIT編譯出來的機器碼性能比解釋器高。
- C2編譯器啟動時長比C1編譯器慢,系統(tǒng)穩(wěn)定執(zhí)行以后,C2編譯器執(zhí)行速度遠遠快于C1編譯器。



