JAVA虛擬機學習總結——運行期優化

JAVA虛擬機的運行期優化

解釋器與編譯器

java 程序最初就是通過解釋器進行解釋執行的。當虛擬機發現某個方法或代碼塊的運行特別頻繁時,就會把這些代碼認定爲“熱點代碼”。爲了提高熱點代碼的運行效率,在運行時,虛擬機會把這些代碼編譯成於本地平臺相關的機器碼,並進行各個層次的優化,完成這個任務的編譯器稱爲即時編譯器。

解釋器與編譯器共存

  • 在程序啓動的時候,解釋器可以首先發揮作用,省去編譯時間立即執行。在
    程序運行時,編譯器逐漸發揮作用,把越來越多的代碼編譯成本地代碼,獲取更高的運行效率。
  • 解釋執行節約內存,編譯執行提高效率。
  • 解釋器作爲編譯器激進優化的一個逃生門。

編譯對象與觸發條件

熱點代碼

被多次調用的方法和被多次執行的循環體(編譯的都是整個方法).

熱點探測方式

基於採樣的熱點探測

虛擬機週期性地檢查各個線程的棧頂,如果發現某個方法經常出現在棧頂,就認定爲熱點代碼。簡單高效但是不準確。

基於計數器的熱點探測

虛擬機爲每個方法建立計數器,統計方法執行次數,如果超過一定閾值就認定爲熱點方法。

方法調用計數器

統計方法被調用的次數。

回邊計數器

統計一個方法中循環體代碼的執行次數,在字節碼中遇到控制流向後跳轉的指令稱爲回邊。

方法調用計數器熱度的衰減

方法調用計數器統計的並不是方法被調用的絕對次數,而是一個相對的執行頻率,即一段時間之內方法被調用的次數,當超過一定的時間限度,如果方法的調用次數仍然不足以讓它提交給即時編譯器編譯,那這個方法的調用計數器就會被減少一半,這個過程就是方法調用計數器熱度的衰減。但是回邊計數器就是統計該方法循環體的絕對執行次數。

編譯過程

第一階段

一個平臺獨立的前端將字節碼構成一種高級中間代碼表示(HIR)。HIR使用靜態單分配的形式代表代碼值,這可以使得HIR的構造過程之中或之後進行的優化動作更容易實現。一些基礎的優化如方法內聯,常量傳播在HIR被構成之前完成。

第二階段

一個平臺相關的後端從HIR中產生低級中間代碼表示(LIR),在此之前會在HIR基礎上完成諸如空值檢查消除,範圍檢查消除的優化。以便讓HIR達到更高效的代碼表示形式。

第三階段

在平臺相關的後端使用線性掃描算法,在LIR上分配寄存器,並在LIR上做窺孔優化,然後產生機械代碼。

幾個經典的優化手段

公共子表達式消除

如果一個表達式E已經計算過了,並且先前的計算到現在E中的變量的值都麼有發生變化,那麼這次E的結果就可以複用上次的結果。

數組邊界消除

當訪問一個對象極少出現異常的時候,我們可以假設這個對象不會不會出現異常,然後取消對它的異常判斷(如不判斷是否爲空),然後用try.catch進行異常捕獲。這時可以節約一次判斷時間,但只適合於極少出現異常的情況。

方法內聯

消除方法調用的成本,爲其他優化手段奠定基礎。

  • 直接內聯:適合於私有方法,實例構造器,父類方法,靜態方法,被final修飾的方法等這些非虛方法。
  • 守護內聯:對於非虛方法,如果當前程序只有一個目標版本,也會先進行激進內聯。如果程序後續執行過程中沒有加載到會令這個方法的接受者的繼承關係發生變化的類,那這個內聯優化的代碼就會一直使用下去,否則就會拋棄已經編譯的代碼,退回到解釋狀態執行,或者重新進行編譯。
  • 內聯緩存:對於非虛方法,如果有多個目標版本,會使用內聯緩存進行內聯,這是一個建立在目標方法正常入口之前的緩存,它的工作原理大致是:在未發生方法調用之前,內聯緩存狀態爲空,當地一次調用之後,緩存記錄下方法接受者的版本信息,並且每次進行方法調用時都比較接受者的版本,如果以後進來的每次調用的方法接受者版本都是一樣的,那這個內聯還可以一直用下去,如果發生了不一樣的情況,說明程序使用了虛方法的多態特性,纔會取消內聯,查找虛方法表進行分派。
逃逸分析

分析對象的動態作用域。

  • 方法逃逸:一個對象在方法中被定義後,它可能被外部方法所引用,例如作爲參數傳遞到其他方法中。
  • 線程逃逸:一個對象被定義後,可能被外部線程訪問到,例如賦值給類變量或可以在其他線程中訪問的實例變量。

對於不會有逃逸現象的對象,可以進行如下優化:

  • 棧上分配:將不會有方法逃逸現象的對象分配到棧上,對象所佔的空間就隨着棧幀出棧而銷燬了,減小垃圾收集系統的壓力。
  • 同步消除:對不會有線程逃逸現象的對象,消除其同步措施。
  • 標量替換:對於沒有逃逸現象,也可以被拆散爲標量的對象,程序真正執行的時候可能不會創建這個對象,而是直接創建若干個被這個方法使用到的成員變量來代替,這樣可以讓這些成員變量分配到棧上,也能爲後續優化提供便利。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章