JIT即時編程-方法內聯

什麼是JIT

JIT的全稱是Just in time compilation,中文稱之爲即時編譯,能夠加速 Java 程序的執行速度。

在JVM剛啓動的時候,java都是解釋執行的,只有java程序運行足夠時間後,jvm會挑選出來熱點的方法和循環,將他編譯成本地機器碼。
JIT 的編譯過程:
在這裏插入圖片描述

JIT是JVM最強大的武器之一,在運行時可以讓java從屌絲到高富帥的飛躍,強大到很多人產生了java比C++快的幻覺,在google上搜索爲什麼java比c++快居然會有200W的結果。通常JIT的利用以下幾種手段來優化JVM的性能

  1. 針對特定CPU型號的編譯優化,JVM會利用不同CPU支持的SIMD指令集來編譯熱點代碼,提升性能。像intel支持的SSE2指令集在特定情況下可以提升近40倍的性能。
  2. 減少查表次數。比如調用Object.equals()方法,如果運行時發現一直是String對象的equals,編譯後的代碼可以直接調用String.equals方法,跳過查找該調用哪個方法的步驟。
  3. 逃逸分析。JAVA變量默認是分配在主存的堆上,但是如果方法中的變量未逃出使用的生命週期,不會被外部方法或者線程引用,可以考慮在棧上分配內存,減少GC壓力。另外逃逸分析可以實現鎖優化等提升性能方法。
  4. 寄存器分配,部分變量可以分配在寄存器中,相對於主存讀取,更大的提升讀取性能。
  5. 針對熱點代碼編譯好的機器碼進行緩存。代碼緩存具有固定的大小,並且一旦它被填滿,JVM 則不能再編譯更多的代碼。
  6. 方法內聯,也是JIT實現的非常有用的優化能力,同時是開發者能夠簡單參與JIT性能調優的地方。

討論JIT-方法內聯的原理,以及如何在代碼中實現方法內聯。

方法內聯是什麼。爲什麼它能夠提升性能

要搞清楚爲什麼方法內聯有用,首先要知道當一個函數被調用的時候發生了什麼

  1. 首先在內存中會有個方法的執行棧,存儲目前所有活躍的方法,以及它們的本地變量和參數
  2. 當一個新的方法被調用了,一個新的棧幀會被加到棧頂,分配的本地變量和參數會存儲在這個棧幀
  3. 跳到目標方法代碼執行
  4. 方法返回的時候,本地方法和參數會被銷燬,棧頂被移除
  5. 返回原來地址執行

這就是通常說的函數調用的壓棧和出棧過程,因此,函數調用需要有一定的時間開銷和空間開銷,當一個方法體不大,但又頻繁被調用時,這個時間和空間開銷會相對變得很大,變得非常不划算,同時降低了程序的性能。根據二八原則,80%的性能消耗其實是發生在20%的代碼上,對熱點代碼的針對性優化可以顯著的提升系統的性能。

方法內聯的原理

方法內聯就是把被調用方函數代碼"複製"到調用方函數中,來減少因函數調用開銷的技術。

我們寫一個簡單的兩數相加程序,被內聯前的代碼

private int add4(int x1, int x2, int x3, int x4) {  
    return add2(x1, x2) + add2(x3, x4);  
}  

private int add2(int x1, int x2) {  
    return x1 + x2;  
}

運行一段時間後,代碼被內聯翻譯成

private int add4(int x1, int x2, int x3, int x4) {  
    return x1 + x2 + x3 + x4;  
} 

方法內聯的條件

JVM會自動的識別熱點方法,並對它們使用方法內聯優化。那麼一段代碼需要執行多少次纔會觸發JIT優化呢?通常這個值由-XX:CompileThreshold參數進行設置:

  • 使用client編譯器時,默認爲1500;
  • 使用server編譯器時,默認爲10000;

但是一個方法就算被JVM標註成爲熱點方法,JVM仍然不一定會對它做方法內聯優化。其中有個比較常見的原因就是這個方法體太大了,分爲兩種情況。

  • 如果方法是經常執行的,默認情況下,方法大小小於325字節的都會進行內聯(可以通過** -XX:MaxFreqInlineSize=N**來設置這個大小)
  • 如果方法不是經常執行的,默認情況下,方法大小小於35字節纔會進行內聯(可以通過** -XX:MaxInlineSize=N **來設置這個大小)

我們可以通過增加這個大小,以便更多的方法可以進行內聯;但是除非能夠顯著提升性能,否則不推薦修改這個參數。因爲更大的方法體會導致代碼內存佔用更多,更少的熱點方法會被緩存,最終的效果不一定好。

如果想要知道方法被內聯的情況,可以使用下面的JVM參數來配置

-XX:+PrintCompilation //在控制檯打印編譯過程信息
-XX:+UnlockDiagnosticVMOptions //解鎖對JVM進行診斷的選項參數。默認是關閉的,開啓後支持一些特定參數對JVM進行診斷
-XX:+PrintInlining //將內聯方法打印出來

方法內聯的其他隱含條件

雖然JIT號稱可以針對代碼全局的運行情況而優化,但是JIT對一個方法內聯之後,還是可能因爲方法被繼承,導致需要類型檢查而沒有達到性能的效果

想要對熱點的方法使用上內聯的優化方法,最好儘量使用final、private、static這些修飾符修飾方法,避免方法因爲繼承,導致需要額外的類型檢查,而出現效果不好情況。

結論

針對熱點方法,想要通過JIT內聯優化來提升性能的建議

更小的方法體,JVM總是偏好更小的方法。

儘量使用final、private、static修飾符

使用+PrintInlining參數校驗效果

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