我看Java虛擬機(7)---解釋器和JIT編譯器

Java是被定爲爲解釋性語言,JIT編譯器並不是強制需要的,也並非所有的虛擬機都是用解釋器+編譯器的並存架構。但主流的商用虛擬機如Hotspot、J9等都採用這種並存的架構。

解釋器和編譯器比較

解釋器優點:省去編譯時間,啓動速度快
編譯器優點:對代碼進行優化,執行效率高
兩種方式的優點各爲對方的缺點。即解釋器的缺點是執行效率低下,編譯器的缺點是啓動速度慢。很容易理解。

Java虛擬機

由於Java虛擬機的平臺無關性,它比C編譯器多了一個字節碼的步驟,所以會顯得性能效率比較雞肋。所以Java虛擬機採用這種解釋器和編譯器(兩種模式:client(也叫c1)模式和server(c2)模式)並存的方式,對速度和效率做了一個trade off。
簡單來講,Java虛擬機就是將使用頻率高的“熱點代碼”,觸發即時編譯器將其編譯爲本地代碼,以後每次使用時直接調用本地代碼。那麼問題來了:

  1. 怎麼判定熱點代碼
  2. 判定出來後怎麼處理

熱點代碼的判定

目前主要有兩種方式探測熱點代碼:
1. 基於採樣的熱點探測:虛擬機週期性檢測各個線程的棧頂方法,若是某個方法經常出現,則判定該方法爲熱點代碼。
優點:實現簡單;
缺點:不精確,比如當某個線程處於線程阻塞時,會擾亂熱點探測。
2. 基於計數器的熱點探測:爲每個方法建立計數器,統計執行次數,若超過一定閾(yu)值,則認爲它是“熱點方法”。
優點:精確;
缺點:實現複雜,要爲每個方法維護計數器。

熱點代碼的處理

基於計數器的探測,它爲每個方法準備了兩個計數器:方法調用計數器和回邊計數器。
這兩個計數器都有各自的一個閾值,若超過閾值則觸發JIT編譯。

  1. 方法調用計數器:用於統計該方法被調用的次數,c1模式下默認5000次,c2模式下10000次。執行過程如下:判斷是否已存在編譯版本,如已存在,則執行編譯版本;否則,方法計數器+1,判斷兩個計數器之和(注意:是方法計數器和回邊計數器的和)是否超過方法計數器閾值,超過則向編譯器提交編譯請求,然後和不超過閾值情況下的處理方式一樣,仍舊解釋執行該方法。
    PS:該計數器並不是絕對次數,而是相對的執行次數,即在一段時間內的執行次數,當超過一定的時間限度,若還是沒有達到閾值,那麼它的計數器會減少一半,此過程被稱爲熱度衰減。
  2. 回邊計數器:用於統計方法中循環體代碼的執行次數,字節碼中遇到控制流向後跳轉的指令稱爲“回邊”。建立該計數器的目的就是爲觸發OSR(On StackReplacement)編譯,即棧上替換。
    和方法計數器執行過程不同的是:當兩個計數器之和超過閾值的時候,它向編譯器提交OSR編譯,並且調整回邊計數器值,然後仍舊以解釋方式執行下去。
    PS:該計數器是絕對次數,沒有熱度衰減。

其他編譯優化技術

  1. 公共子表達式消除int d = (c * b) * 12 +(a + b * c)不優化時,b*c將會被計算兩次,優化以後就相當於:
E = c * b;
int d = E * 12 + (a + E);

省去一次計算。
2. 數組邊界檢查消除
比如:前面代碼使用過f[3],並且判斷了0<=3<=f.length,則下次使用f[3]時,判斷可省略。
3. 方法內聯:對於非虛方法就不講了;對於Java的虛方法,方法內聯就會出現問題,到底選擇哪一個版本的實現,就是一個問題,Java虛擬機引入了一種名爲“類型繼承關係分析”(Class Hierarchy Analysis,CHA)的技術。 該方法檢測出多個版本的實現時,它使用內聯緩存,即,第一次調用發生時,記錄該接收者信息,緩存該方法,下次發生調用時,比較接收者信息,相同則使用緩存中的方法,否則取消該內聯緩存。
4.逃逸分析:分析對象的動態作用域,當一個對象在方法裏被定義後,他可能被外部方法調用,這種行爲被稱爲方法逃逸。甚至還可能被外部線程訪問,這種行爲被稱爲線程逃逸。 如果能保證一個對象不發生方法逃逸或線程逃逸,則就能對這個變量進行一些高校的優化,比如:棧上分配,同步消除,標量替換等。

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