後端編譯與優化
阿新 • • 發佈:2021-02-07
> 本書部分摘自《深入理解 Java 虛擬機器第三版》
## 概述 前面講過前端編譯是將 Java 原始碼編譯成 Class 位元組碼,那麼後端編譯就對應把 Class 檔案轉換成與本地機器相關的二進位制機器碼的過程。然後 JVM 把每一條要執行的位元組碼交給直譯器,翻譯成對應的機器碼,由直譯器執行,Java 程式就執行起來了
## 即時編譯器 當虛擬機發現某個方法或程式碼塊執行特別頻繁,就會把這些程式碼認定為熱點程式碼(HotSpot Code),為了提高熱點程式碼的執行效率,在執行時,虛擬機器將會把這些程式碼編譯為本地機器碼,並以各種手段進行程式碼優化,在執行時完成這個任務的後端編譯器被稱為即時編譯器 #### 1. 編譯物件 熱點程式碼主要有兩類: - 被多次呼叫的方法 - 被多次執行的迴圈體 對於這兩種情況,編譯的目標物件都是整個方法體。第一種情況,由於是依靠方法呼叫觸發的編譯,以整個方法為編譯物件毫無疑問。而後一種情況,雖然編譯器仍以整個方法作為編譯物件,但執行入口(從方法第幾條位元組碼執行開始執行)會稍有不同。 #### 2. 觸發條件 如何判斷熱點程式碼?是不是需要進行即時編譯?這個行為稱為熱點探測(Hot Spot Code Detection),進行熱點探測並不一定要知道方法具體被呼叫了多少次,目前主流的熱點探測判定方式有兩種: - 基於取樣的熱點探測(Sample Based HotSpot Code Detection) 虛擬機器會週期性地檢查各個執行緒的呼叫棧頂,如果發現某個(某些)方法經常出現在棧頂,那這個方法就是熱點方法。這種方式的好處是實現簡單高效,可以很容易獲取方法呼叫關係(將呼叫堆疊展開即可),缺點是很精確地確定一個方法的熱度,容易受執行緒阻塞或別的外界因素的影響 - 基於計數器的熱點探測(Counter Based HotSpot Code Detection) 虛擬機器為每個方法(甚至程式碼塊)建立計數器,統計方法的執行次數,如果執行次數超過一定閾值就認為是熱點方法。這種統計方式實現起來麻煩一些,不能直接獲取方法的呼叫關係,但結果相對來說更加精確 HotSpot 採用基於計數器待熱點探測方法,同時準備了兩類計數器:方法呼叫計數器(Invocation Counter)和回邊計數器(Back Edge Counter,回邊的意思是指在迴圈邊界往回跳轉)。當虛擬機器執行引數確定的前提下,這兩個計數器都有一個明確的閾值,一旦溢位,就會觸發即時編譯。 當一個方法被呼叫時,虛擬機器會先檢查該方法是否存在被即時編譯過後的版本,如果存在,則優先使用編譯後的原生代碼來執行。如果不執行已被即時編譯過後的版本,則將該方法的呼叫計數器值加一,然後判斷方法呼叫計數器與回邊計數器之和是否超過閾值,如果超過,則向即時編譯器提交一個該方法的程式碼編譯請求 如果沒做過任何設定,執行引擎預設不會同步等待編譯請求完成,而是繼續進入直譯器執行位元組碼,直到被提交的請求被即時編譯器編譯完成,當編譯工作完成後,這個方法的呼叫入口地址就會被系統自動改寫成新值,下一次呼叫該方法就會使用已編譯的版本 預設設定下,方法呼叫計數器統計的並不是方法被呼叫的絕對次數,而是一個相對的執行頻率,即一段時間之內方法被呼叫的次數。當超過一定的時間限度,如果方法的呼叫次數仍不足以讓它提交給即時編譯器編譯,那該方法的呼叫計數器就會減半,這個過程稱為方法呼叫計數器熱度的衰減(Counter Decay),這個動作是在虛擬機器進行垃圾收集時順便進行的,也可以關閉熱度衰減,讓虛擬機器統計方法呼叫的絕對次數,這樣時間長了,程式中絕大部分方法都會被編譯成原生代碼 再看一看回邊計數器,它的作用是統計一個方法中迴圈體程式碼執行的次數,在位元組碼中遇到控制流向後跳轉的指令就稱為回邊(Back Edge)。當直譯器遇到一條回邊指令,會先查詢將要執行的程式碼片段是否有已經編譯好的版本,如果有的話,將優先執行已編譯的程式碼,否則就把回邊計數器的值加一,然後判斷方法呼叫器與回邊計數器之和是否超過回邊計數器的閾值。當超過閾值,將提交一個棧上替換編譯請求,並且把回邊計數器的值稍微降低,以便繼續在直譯器中執行迴圈,等待編譯器輸出編譯結果 回邊計數器沒有計數熱度衰減的過程,因此這個計數器統計的就是方法迴圈執行的絕對次數。當計數器溢位時,它還會把方法計數器的值也調整為溢位狀態,這樣下次再進入這個方法的時候就會執行標準編譯過程了