Java的JIT


什么是JIT:

JIT編譯器(just in time 即時編譯器),當虛擬機發現某個方法或代碼塊運行特別頻繁時,就會把這些代碼認定為(Hot Spot Code 熱點代碼,為了提高熱點代碼的執行效率,在運行時,虛擬機將會把這些代碼編譯成與本地平台相關的機器碼,並進行各層次的優化,完成這項任務的正是JIT編譯器。
目前主要的熱點 判定方式有以下兩種:

  1. 基於采樣的熱點探測:
    采用這種方法的虛擬機會周期性地檢查各個線程的棧頂,如果發現某些方法經常出現在棧頂,那這段方法代碼就是“熱點代碼”。這種探測方法的好處是實現簡單高效,還可以很容易地獲取方法調用關系,缺點是很難精確地確認一個方法的熱度,容易因為受到線程阻塞或別的外界因素的影響而擾亂熱點探測。
  2. 基於計數器的熱點探測:
    采用這種方法的虛擬機會為每個方法,甚至是代碼塊建立計數器,統計方法的執行次數,如果執行次數超過一定的閥值,就認為它是“熱點方法”。這種統計方法實現復雜一些,需要為每個方法建立並維護計數器,而且不能直接獲取到方法的調用關系,但是它的統計結果相對更加精確嚴謹。
    HotSpot虛擬機中使用的是第二種:基於計數器的熱點探測方法,因此它為每個方法准備了兩個計數器:方法調用計數器和回邊計數器。
    • 方法調用計數器
    方法調用計數器用來統計方法調用的次數,在默認設置下,方法調用計數器統計的並不是方法被調用的絕對次數,而是一個相對的執行頻率,即一段時間內方法被調用的次數。
    • 回邊計數器
    用於統計一個方法中循環體代碼執行的次數(准確地說,應該是回邊的次數,因為並非所有的循環都是回邊),在字節碼中遇到控制流向后跳轉的指令就稱為“回邊”。
    JIT編譯。觸發了JIT編譯后,在默認設置下,執行引擎並不會同步等待編譯請求完成,而是繼續進入解釋器按照解釋方式執行字節碼,直到提交的請求被編譯器編譯完成為止(編譯工作在后台線程中進行)。當編譯工作完成后,下一次調用該方法或代碼時,就會使用已編譯的版本。
    方法調用計數器觸發即時編譯的流程(回邊計數器觸發即時編譯的過程類似)
    JIT優化JVM的性能
    通常JIT的有以下幾種手段來優化JVM的性能
  3. 針對特定CPU型號的編譯優化,JVM會利用不同CPU支持的SIMD指令集來編譯熱點代碼,提升性能。像intel支持的SSE2指令集在特定情況下可以提升近40倍的性能。
  4. 減少查表次數。比如調用Object.equals()方法,如果運行時發現一直是String對象的equals,編譯后的代碼可以直接調用String.equals方法,跳過查找該調用哪個方法的步驟。
  5. 逃逸分析。JAVA變量默認是分配在主存的堆上,但是如果方法中的變量未逃出使用的生命周期,不會被外部方法或者線程引用,可以考慮在棧上分配內存,減少GC壓力。另外逃逸分析可以實現鎖優化等提升性能方法。
  6. 寄存器分配,部分變量可以分配在寄存器中,相對於主存讀取,更大的提升讀取性能。
  7. 針對熱點代碼編譯好的機器碼進行緩存。代碼緩存具有固定的大小,並且一旦它被填滿,JVM 則不能再編譯更多的代碼。
  8. 方法內聯,也是JIT實現的非常有用的優化能力,同時是開發者能夠簡單參與JIT性能調優的地方。

方法內聯是什么。為什么它能夠提升性能

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

  1. 首先會有個棧,存儲目前所有活躍的方法,以及它們的本地變量和參數
  2. 當一個新的方法被調用了,一個新的棧幀會被加到棧頂,分配的本地變量和參數會存儲在這個棧幀
  3. 跳到目標方法代碼執行
  4. 方法返回的時候,本地方法和參數會被銷毀,棧頂被移除
  5. 返回原來地址執行
    因此,函數調用需要有一定的時間開銷和空間開銷,當一個方法體不大,但又頻繁被調用時,這個時間和空間開銷會相對變得很大,變得非常不划算,同時降低了程序的性能。
    方法內聯就是把被調用方函數代碼"復制"到調用方函數中,來減少因函數調用開銷的技術。
    被內聯前的代碼
    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參數進行設置:
    1、使用client編譯器時,默認為1500;
    2、使用server編譯器時,默認為10000;
    但是一個方法就算被JVM標注成為熱點方法,JVM仍然不一定會對它做方法內聯優化。其中有個比較常見的原因就是這個方法體太大了,分為兩種情況。
    • 如果方法是經常執行的,默認情況下,方法大小小於325字節的都會進行內聯(可以通過** -XX:MaxFreqInlineSize=N來設置這個大小)
    • 如果方法不是經常執行的,默認情況下,方法大小小於35字節才會進行內聯(可以通過
    -XX:MaxInlineSize=N **來設置這個大小)
    我們可以通過增加這個大小,以便更多的方法可以進行內聯;但是除非能夠顯著提升性能,否則不推薦修改這個參數。因為更大的方法體會導致代碼內存占用更多,更少的熱點方法會被緩存,最終的效果不一定好。
    如果想要知道方法被內聯的情況,可以使用下面的JVM參數來配置
    -XX:+PrintCompilation: Prints out when JIT compilation happens
    -XX:+UnlockDiagnosticVMOptions: Is needed to use flags like -XX:+PrintInlining
    -XX:+PrintInlining: Prints what methods were inlined

結論

熱點方法的內聯優化建議

  1. 更小的方法體
  2. 盡量使用final、private、static修飾符
  3. 使用+PrintInlining參數校驗效果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM