本書部分摘自《深入理解 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),當解釋器遇到一潭訓邊指令,會先查找將要執行的代碼片段是否有已經編譯好的版本,如果有的話,將優先執行已編譯的代碼,否則就把回邊計數器的值加一,然后判斷方法呼叫器與回邊計數器之和是否超過回邊計數器的閾值,當超過閾值,將提交一個堆疊上替換編譯請求,并且把回邊計數器的值稍微降低,以便繼續在解釋器中執行回圈,等待編譯器輸出編譯結果
回邊計數器沒有計數熱度衰減的程序,因此這個計數器統計的就是方法回圈執行的絕對次數,當計數器溢位時,它還會把方法計數器的值也調整為溢位狀態,這樣下次再進入這個方法的時候就會執行標準編譯程序了
提前編譯器
現在提前編譯器的研究兩條明顯的分支:在程式運行前把程式代碼編譯成機器碼的靜態翻譯作業,以及把原本即時編譯器在運行時要做的編譯作業提前做好并保存,下次運行到這些代碼時直接把它加載進來使用
第一種傳統的提前編譯應用形式,它是為了解決即時編譯的最大弱點:即時編譯要占用程式的運行時間和時間資源,而第二種方式,本質是給即時編譯器做快取加速,
提前編譯器因為沒有執行時間和資源限制的壓力,可以毫無忌憚地使用重負載的優化手段,這是一個極大的優勢,但即時編譯器也有它的長處:
-
性能分析制導優化(Profile-Guided Optimization)
即時編譯器在運行程序中,會不斷地收集性能監控資訊,譬如條件判斷通常走哪個分支、回圈會進行幾次等等,這些資料一般在靜態分析時是無法得到的,或者說不能得到一個明確的解,但在動態運行時卻能看出它們具有非常明顯的偏好性,比如一個條件分支的某一路徑執行頻繁,就可以對熱點代碼進行優化和分配更多的資源
-
激進預測性優化(Aggressive Speculative Optimization)
靜態優化必須保證優化前后的程式對外部可見影響(不僅僅是執行結果)是等效的,而即時編譯可以不必如此保守,如果性能監控監控資訊能夠支持它做出一些正確的可能性很大但無法保證絕對正確的預測判斷,就可以大膽地按照高概率的假設進行優化,萬一真的走到罕見分支上,大不了退回到低級編譯器甚至解釋器上去執行,并不會出現無法挽救的后果
-
鏈接時優化
Java 語言天生就是動態鏈接的,一個個 Class 檔案在運行期被加載到虛擬機記憶體中,然后在即時編譯器里產生優化后的本地代碼
編譯器優化技術
編譯器的目標雖然是做由程式代碼翻譯為本地機器碼的作業,但其難點并不在于能否成功翻譯出機器碼,輸出代碼優化質量才是決定編譯器優秀與否的關鍵
1. 方法行內
方法行內就是把目標方法的代碼原封不動地復制到發起呼叫的方法之中,避免發生真實的方法呼叫,方法行內聽上去很簡單,但實作并不簡單,因為有方法決議和分派機制,只有使用 invokespecial 指令呼叫的私有方法、實體構造器、父類方法、使用 invokestatic 指令呼叫的靜態方法和被 final 修飾的方法,這些方法會在編譯器決議,而其他 Java 方法必須在運行時進行方法接收者的多型選擇,它們都有可能有多于一個版本的方法接收者
為了解決這個難題,Java 虛擬機引入了一種名為型別繼承關系分析的技術,用于確定在目前已加載的類中,某個介面是否有多于一種的實作、某個類是否存在子類、某個子類是否覆寫了父類的某個虛方法等資訊,這樣,如果遇到非虛方法,直接行內即可,如果查到只有一個版本,也直接行內,這種行內稱為守護行內(Guarded Inlining),不過由于 Java 程式是動態連接的,有可能會有新的型別加載進來,所以守護行內屬于激進預測性優化,必須預留好退路,假如繼承關系發生變化,那么就必須拋棄已編譯的代碼,退回到解釋狀態進行執行,或重新編譯
如果方法存在多個版本的目標方法可供選擇,虛擬機將使用行內快取(Inline Cache)來縮減方法呼叫的開銷,行內快取是一個建立在目標方法正常入口之前的快取,如果未發生方法呼叫,行內快取狀態為空,當第一次呼叫發生后,快取記錄下方法接收者的版本資訊,并且每次進行方法呼叫時都比較接收者的版本,如果每次都一致,就直接使用,否則查找虛方法表進行方法分派
2. 逃逸分析
逃逸分析的基本原理是:分析物件動態作用域,當一個物件在方法里面被定義后,它可能被外部方法參考,例如作為呼叫引數傳到其他方法中,這稱為方法逃逸;甚至有可能被外部執行緒訪問,這稱為執行緒逃逸
根據一個物件的逃逸程度,可以進行不同程度的優化:
-
堆疊上分配
物件是在堆疊上分配記憶體的,主要持有這個物件的參考,就可以訪問堆中存盤的物件資料,如果確定一個物件不會逃逸出執行緒之外,可以讓這個物件在堆疊上分配分配記憶體
-
標量替換
若一個資料已經無法再分解成更小的資料表示,如原始資料型別,那么這些資料就稱為標量,相對的,如果一個資料可以繼續分解,那就稱為聚合量,如物件,如果一個物件不會被方法外部訪問,那這個物件就可以拆成多個標量,替換原來參考物件的成員變數的地方
-
同步消除
如果一個變數不會逃逸出執行緒,那么這個變數的讀寫肯定不會有競爭,對這個變數實施的同步措施也就可以安全地消除掉
3. 公共子運算式消除
如果一個運算式 E 之前已經被計算過,而且從先前的計算到現在 E 中所有變數的值都沒有變化,那么 E 就稱為公共子運算式,之后就沒有必要再花時間重新計算了,直接用之前的計算結果代替 E 即可
假設有如下代碼
int d = (c * b) * 12 + a + (a + b * c);
編譯器檢測到 c * b 和 b * c 是一樣的運算式,而且 b 與 c 的值不變,因此這條運算式可能被視為
int d = E * 12 + a + (a + E);
4. 陣列邊界檢查消除
我們知道 Java 中的陣列不能越界訪問,否則會拋出一個運行時例外,這得益于系統會自動進行背景關系的范圍檢查,但如果每次對陣列元素的讀寫都要檢查一次,無疑是一種負擔,可無論如何,陣列邊界安全檢查是肯定要做的,不過虛擬機會在編譯期根據資料分析流判斷陣列下標有沒有越界的可能,避免過多的開銷
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/257649.html
標籤:其他
