JVM編譯優化





在部分的商用虛擬機中,Java 程序最初是通過解釋器(Interpreter )進行解釋執行的,當虛擬機發現某個方法或代碼塊的運行特別頻繁的時候,就會把這些代碼認定爲“熱點代碼”。爲了提高熱點代碼的執行效率,在運行時,即時編譯器(Just In Time Compiler )會把這些代碼編譯成與本地平臺相關的機器碼,並進行各種層次的優化。

HotSpot 內的即時編譯器

解釋器和編譯器各有各的優點

  • 解釋器優點:當程序需要迅速啓動的時候,解釋器可以首先發揮作用,省去了編譯的時間,立即執行。解釋執行佔用更小的內存空間。同時,當編譯器進行的激進優化失敗的時候,還可以進行逆優化來恢復到解釋執行的狀態。

  • 編譯器優點:在程序運行時,隨着時間的推移,編譯器逐漸發揮作用根據熱點探測功能,,將有價值的字節碼編譯爲本地機器指令,以換取更高的程序執行效率。
    因此,整個虛擬機執行架構中,解釋器與編譯器經常配合工作。

HotSpot中內置了兩個即時編譯器,分別稱爲 Client Compiler和 Server Compiler ,或者簡稱爲 C1編譯器和 C2編譯器。

目前的 HotSpot編譯器默認的是解釋器和其中一個即時編譯器配合的方式工作,具體是哪一個編譯器,取決於虛擬機運行的模式,HotSpot虛擬機會根據自身版本與計算機的硬件性能自動選擇運行模式

用戶也可以使用 -client和 -server參數強制指定虛擬機運行在 Client模式或者 Server模式。這種配合使用的方式稱爲“混合模式”(Mixed Mode)

用戶可以使用參數 -Xint 強制虛擬機運行於 “解釋模式”(Interpreted Mode),這時候編譯器完全不介入工作。

另外,使用 -Xcomp 強制虛擬機運行於 “編譯模式”(Compiled Mode),這時候將優先採用編譯方式執行,但是解釋器仍然要在編譯無法進行的情況下接入執行過程。通過虛擬機 -version 命令可以查看當前默認的運行模式。

被編譯對象和觸發條件

在運行過程中會被即時編譯的“熱點代碼”有兩類
- 被多次調用的方法
- 被多次執行的循環體

對於第一種,編譯器會將整個方法作爲編譯對象,這也是標準的JIT 編譯方式。

對於第二種是由循環體出發的,但是編譯器依然會以整個方法作爲編譯對象,因爲發生在方法執行過程中,稱爲棧上替換。

判斷一段代碼是否是熱點代碼,是不是需要出發即時編譯,這樣的行爲稱爲熱點探測(Hot Spot Detection),探測算法有兩種,分別爲。

  • 基於採樣的熱點探測(Sample Based Hot Spot Detection):虛擬機會週期的對各個線程棧頂進行檢查,如果某些方法經常出現在棧頂,這個方法就是“熱點方法”。好處是實現簡單、高效,很容易獲取方法調用關係。缺點是很難確認方法的reduce,容易受到線程阻塞或其他外因擾亂。
  • 基於計數器的熱點探測(Counter Based Hot Spot Detection):爲每個方法(甚至是代碼塊)建立計數器,執行次數超過閾值就認爲是“熱點方法”。優點是統計結果精確嚴謹。缺點是實現麻煩,不能直接獲取方法的調用關係。
    HotSpot 使用的是第二種-基於技術其的熱點探測,並且有兩類計數器:方法調用計數器(Invocation Counter)和回邊計數器(Back Edge Counter)。
    這兩個計數器都有一個確定的閾值,超過後便會觸發 JIT 編譯。

編譯過程

編譯期優化(早期優化)

爲了保證JRuby,Groovy等語言編譯的字節碼也能得到性能優化,JVM將性能優化放在了後期的運行時優化,即JIT運行時編譯優化中。
具體優化:
1. 編譯期優化主要爲語法糖,用來實現Java的各種新的語法特性,比如泛型,變長參數,自動裝箱/拆箱。
2. Java語法糖:與字節碼無關,編譯後會去掉它們。作用僅僅爲方便碼農寫代碼,以及將運行時異常在編譯期及早發現(如泛型的使用)。
3. 泛型與類型擦除
Java泛型只在編譯期存在,編譯完成後的字節碼中會替換爲原生類型。故稱Java泛型爲僞泛型。C#的泛型在運行期仍然存在。
4. 條件編譯
if語句中使用常量。比如if(false) {},這個語句塊不會被編譯到字節碼中.這個過程在編譯時的控制流分析中完成。

【聲明:轉載請註明出處
獨立:http://wangnan.tech
簡書:http://www.jianshu.com/u/244399b1d776
CSDN:http://blog.csdn.net/wangnan9279

運行時優化(晚期優化)

不同JVM的運行時優化策略
Hotspot採用解釋器與編譯器並存的構架。
第0層,解釋執行,不開啓性能監控器,可觸發第一層編譯
第1層,將字節碼編譯爲機器碼,進行簡單可靠的優化,可以開啓性能監控
第2層,將字節碼編譯爲機器碼,會開啓一些編譯耗時的優化和一些不可靠的激進

具體優化

  • 公共字表達式消除
    如果一個表達式E已經被計算過了,並且從先前的計算到現在E中所有變量的值都沒有發生變化,那麼E的這次出現就稱爲了公共子表達式。對於這種表達式,沒有必要花時間再對它進行計算,只需要直接用前面計算過的表達式結果代替E就可以了。

  • 數組邊界檢查消除
    數組邊界檢查消除(Array Bounds Checking Elimination)是即時編譯器中的一項語言相關的經典優化技術。Java訪問數組的時候系統將會自動進行上下界的範圍檢查,但對於虛擬機的執行子 系統來說,每次數組元素的讀寫都帶有一次隱含的條件判定操作,對於擁有大量數組訪問的程序代碼,這無疑也是一種性能負擔。
    數組邊界檢查時必須做的,但數組邊界檢查在某些情況下可以簡化。例如數組下標示一個常量,如foo3,只要在編譯器根據數據流分析來確定foo.length的值,並判斷下標“3”沒有越界,執行的時候就無須判斷了。再例如數組訪問發生在循環之中,並且使用循環變量來進行數組訪問,如果編譯器只要通過數據流分析就可以判定循環變量的取值範圍永遠在區間[0, foo.length)之內,那在整個循環中就可以把數組的上下界檢查消除掉,這可以節省很多次的條件判斷操作。
    與語言相關的其他消除操作還有自動裝箱消除(Autobox Elimination)、安全點消除(Safepoint Elimination)、消除反射(Dereflection)等。

  • 方法內聯
    方法內聯是編譯器最重要的優化手段之一,除了消除方法調用的成本之外,更重要的是可以爲其他優化手段建立良好的基礎。
    逃逸分析
    逃逸分析(Escape Analysis)並不是直接優化代碼的手段,而是爲其他優化手段提供依據的分析技術。

參考:
- http://blog.csdn.net/chdjj/article/details/24010581
- http://blog.csdn.net/qq_16681169/article/details/72945113
- http://yueyemaitian.iteye.com/blog/1185297
- http://ifeve.com/jvm-compiler/

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