想買保時捷的運維李先生學Java效能之 JIT即時編譯器
前言
本文記錄日常學習《深入理解Java虛擬機器》,不知道為啥感覺看一遍也就過了,喜歡動動手理解理解,這樣才有點感覺,靜不下心來的時候,看書抄書也可以用這個辦法。
一、什麼是JIT(Just In Time Compiler)即時編譯器
在虛擬機器中(Sun HotSpot),Java程式最初是通過直譯器執行的,當虛擬機發現某個方法或程式碼塊的執行特別頻繁時,就會把這些程式碼認定為“熱點程式碼”(Hot Spot Code)。為了提高熱點程式碼的執行效率,在執行時,虛擬機器會把這些程式碼編譯成與本地平臺相關的機器碼,並進行各種層次的優化,完成這個任務的編譯器稱為即時編譯器(Just In Time Compiler)。
Java虛擬機器規範並沒有規定java虛擬機器內必須要有即時編譯器存在,更沒有限定和指導即時編譯器應該如何去實現。但是即時編譯器的好壞、程式碼優化程度的高低卻是衡量一款商用虛擬機器優秀與否的最關鍵指標之一,它也是虛擬機器中最核心最能體現虛擬機器水平的部分。
二、JVM內建的兩個即時編譯器
C1編譯器:Client Compiler
C2編譯器:Server Compiler
HotSpot虛擬機器,預設採用直譯器與其中一個編譯器直接配合的方式工作,程式使用哪個編譯器,取決於虛擬機器執行的模式,HotSPot虛擬機器會根據自身版本與宿主機器的硬體效能自動選擇執行模式。也可以使用“-client”和“-server”引數去強制指定虛擬機器執行在Client模式還是Server模式。
[root@ip-10-0-0-90 ~]# java -version java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode) [root@ip-10-0-0-90 ~]# java -Xint -version java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM)64-Bit Server VM (build 25.151-b12, interpreted mode) [root@ip-10-0-0-90 ~]# java -Xcomp -version java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, compiled mode)
三、JVM的分層策略
第0層:程式解釋執行,直譯器不開啟效能監控功能(Profiling),可觸發第一層編譯。
第1層:也稱C1編譯,將位元組碼編譯為原生代碼,進行簡單、可靠的優化,如有必要將加入效能監控的邏輯。
第2層(或2層以上):也稱C2編譯,將位元組碼編譯為原生代碼,但是會啟用一些編譯耗時較長的優化,甚至會根據效能監控資訊進行一些不可靠的激進優化。
分層策略在JDK8是預設開啟的,C1編譯可獲取更高的編譯速度,C2編譯可獲取更好的編譯質量。
四、編譯物件和觸發條件
編譯物件:
1)被多次呼叫的方法
2)被多次執行的迴圈體
觸發條件:
1)基於取樣的熱點探測
採用這種方法的虛擬機器會週期性的檢查各個執行緒的棧頂,如果發現某個或某些方法經常出現在棧頂,那這個方法就是“熱點方法”。
2)基於計數器的熱點探測
採用這種方法的虛擬機器會為每個方法(或程式碼塊)建立計數器,統計方法的執行次數,如果執行次數超過一定的值就認為他是“熱點方法”。
五、JIT編譯方式和OSR編譯方式
JIT編譯方式
方法呼叫觸發的編譯,編譯器會以整個方法作為編譯物件,這種編譯也是虛擬機器中標準的JIT編譯方式。
OSR編譯方式
迴圈體呼叫觸發的編譯即使是由迴圈體觸發的,但編譯器會以整個方法(而不是單獨的迴圈體)作為編譯物件。這種編譯方式因為編譯發生在方法執行過程中,因此形象的稱為棧上替換(On Stack Replacement)簡稱OSR編譯,即方法棧幀還在棧上,方法就被替換了。
六、方法計數器和回邊計數器
在HotSpot虛擬機器中,使用的是基於計數器的熱點探測,他為每個方法準備了兩類計數器,方法呼叫計數器(Incocation Counter) 和回邊計數器(Back Edge Counter)。這兩個計數器都有一個閥值,當計數器超過了這個閥值,就會觸發JIT編譯。
1)方法呼叫計數器
用於統計方法被呼叫的次數,預設閥值為Client模式下:1500次,Server模式下10000次;這個閥值可以通過虛擬機器引數-XX:CompileThreshold設定。當一個方法被呼叫時,會先檢查該方法是否存在被JIT編譯過的版本,如果存在,則優先使用編譯後的原生代碼來執行。如果不存在已被編譯過的版本,則將此方法的呼叫計數器值加1,然後判斷方法呼叫啟與回邊計數器值之和是否超過方法呼叫計數器的閥值,如果超過閥值,那麼將會向即時編譯器提交該方法的程式碼編譯請求。
在預設情況下,執行引擎不會同步等待編譯請求完成,而是繼續進入直譯器按照解釋方式執行位元組碼,直到提交的請求被編譯器編譯完成。當編譯工作完成後,這個方法的呼叫入口地址就會被系統自動改寫成新的,下一次呼叫該方法時就會使用已編譯的版本。
注:
如果不做任何設定,方法呼叫計數器統計的並不是方法被呼叫的絕對次數,而是相對執行頻率,即在一段時間之內方法被呼叫的次數。當超過一定的時間限度,如果方法的呼叫次數仍然不足以讓他提交給即時編譯器,那這個方法的呼叫計數器就會被減少一半,這個過程稱為方法呼叫計數器熱度的衰減(Counter Decay),而這段時間就稱為此方法統計的半衰週期(Counter Half Life Time)。進行熱度衰減的動作是在虛擬機器進行垃圾收集時順便進行的,可以使用虛擬機器引數-XX:-UseCounterDecay來關閉熱度衰減,讓方法計數器統計方法呼叫的絕對次數,這樣只要系統執行時間足夠長,絕大部分方法都會被編譯成原生代碼。另外,可以使用-XX:CounterHalflifeTime引數設定半衰週期的時間,單位為s。
2)回邊計數器
回邊計數器,他的作用是統計一個方法中迴圈體程式碼執行的次數,在位元組碼中遇到控制流向後跳轉的指令稱為“回邊”(back Edge),建立回邊計數器統計的目的就是為了觸發OSR編譯。