爲何Java程序會執行一段時間後跑的更快?



對於Java 應用,程序員之間一個認識口口相傳: 
java

要看一個Java程序跑的快不快,須要多跑幾回;另外,Java程序跑一段時間以後會快起來。速度甚至能遇上 C/C++程序的速度。


若是你問爲何跑一段時間就快了呢?git

通常都能聽到 「由於JVM會把調用次數多的熱方法編譯再執行」的答案。程序員


更通俗的話來說, JVM 會把熱方法編譯成機器碼,執行效率會更高。就像公司或工廠裏,對於一項任務,通常老手都比新人更快,由於老手更熟悉嘛。因此招聘要求裏你不多會見到指明要新人的,大部分都是要有工做經驗的。github


而JVM 將熱方法編譯生成的機器碼,因爲是針對當前平臺,當前硬件生成的,對應用具體執行狀況分析以後進行編譯而成,因此就像老手同樣,能更瞭解狀況,效率固然更高。web


默默在背後作編譯工做的人就是 JIT (Just-In-Time) 編譯器,通常也叫即時編譯器。
數據庫



今天咱們一塊兒來看看,這越跑越快的背後,JIT 具體是怎樣工做的。tomcat


咱們都知道,Java 原生就是解釋型語言,也是解釋執行的,怎麼又有了編譯執行了?微信

執行 java -version 的時候,咱們通常能看到當前 Java 版本號以後,會有一個 mixed mode,說明當前JVM 運行在混合模式之下,即同時包含解釋執行和編譯執行。咱們也能夠經過參數強制執行只按一種模式執行。各類環境根據本身的須要選擇執行的方式。app


相比編譯執行,解釋執行要慢不少,但仍然普遍在被運用在各類虛擬機中,好比它內存佔用少,應用啓動時間更短。更關鍵的優點在於它簡單。一種新語言或者一個語言的新特性出現時,在解釋器中能比編譯器實現要快不少。另外,開發者會考慮到性價比,一些語言特性很難,同時也不值得在實如今編譯器就只使用解釋器。工具


開發實現語言時,使用解釋器只有兩個要求:

  1. 熟悉VM實現語言

  2. 理解新語言特性、語法和語義


而像在JIT編譯器實現新語言特性,對開發者有更多的要求:

  • 熟悉目標機器的應用程序二進制接口規範

  • 把新語言特性映射到這個目標機器的接口運行時

  • 掌握開發編譯器生成目標機器碼的能力



而爲了應用程序的執行效率、運行速度, Java 又特別須要JIT,在運行的適當時候,能夠把一些高頻率代碼編譯,換取更好的效率。


JIT就是經過將熱方法、代碼段編譯生成機器碼的形式,在下次調用到該方法時,會直接經過vtable中連接的機器碼直接執行,因此效率是槓槓的。


那麼問題來了,什麼樣的方法纔算熱方法,怎樣來判斷熱方法?


對於熱方法的計算,通常虛擬機內有如下幾種實現方式:

  • 基於方法的JIT,JVM內經常使用

  • 基於蹤影的JIT, Dalvik和 TraceMonkey在使用

  • 基於區域的JIT,HHVM 使用這種形式


基於方法的JIT中,通常探測熱點方法有基於採樣的熱點探測,即週期性的去檢查線程的調用棧頂,若是方法常常出如今棧頂,那它就是熱點方法。另外一種是基於計數器的熱點探測,這種會給每一個方法創建計數器,用來統計方法的執行次數。超過閾值的就認爲是熱點方法。


固然須要注意的是,這裏統計的次數,不是絕對的次數,和咱們進行限流和降級時說的相似,都是一個時間週期內的相對頻率,若是在此期間沒有超過,就不算,原來的次數會減小。


JIT 編譯的代碼,存儲在 Code Cache 的內存區間。空間是有限的在JVM 啓動的時候,設置了一個固定的最大值,實現形式也是個堆,在分配滿時會中止編譯,類卸載、替換成新版本等也會從 Code Cache中刪除。

另外,在JVM JIT編譯器中包含C一、C2    兩種編譯器,在具體的編譯過程當中,通常是採用分層編譯,再具體使用不一樣的編譯器,相比C1,C2編譯須要更多的時間,作更多的優化等等,像內聯、循環展開、逃逸分析、鎖消除與合併、棧上替換……

前面咱們大概瞭解了JIT的原理,也瞭解到 JIT 編譯後,機器碼執行效率更高,那有什麼辦法能瞭解到咱們本身的應用裏,JIT有沒有執行,用的是C1仍是C2,對哪些代碼作過編譯和優化呢?


咱們有沒有辦法,能知道都有哪些方法被JIT編譯了,哪些方法原本咱們想要效率高一些,期待被編譯卻沒被考慮的,能更直觀的知道呢?

一個辦法是應用啓動時,增長 JVM 參數:

-XX:+UnlockDiagnosticVMOptions

-XX:+PrintCompilation

-XX:+PrintInlining

-XX:+PrintCodeCache

-XX:+PrintCodeCacheOnCompilation

-XX:+TraceClassLoading

-XX:+LogCompilation

-XX:LogFile=~/a.log


而後根據這些輸出內容,以及日誌文件裏的內容,去分析。

固然,若是真的是肉眼閱讀那可太累了。好在有一個優秀的開源工具用於解析日誌文件。

鐺鐺鐺,來了。

就是它, JITWatch。

https://github.com/AdoptOpenJDK/jitwatch

使用 JavaFX 開發而成,功能很強大。



你能夠 經過 Open Log 直接解析上面輸出的日誌文件。  例如一個簡單的應用,打開日誌以後,會看到不一樣包下的內容,這裏example111 是示例。

    public void jitTest() { long x = calc();        System.out.println(x); }
public long calc() { long sum = 0; for (long i=0; i< 1000000; i++) { sum = plus(sum, i); } return sum; }
public long plus(long a, long b) { return a + b; }




在點擊右側某個JIT編譯過的具體方法後,點擊TriView,會看到生成的節字碼,以及相應的源碼是如何對應到字節碼和彙編代碼的。



點擊Chain,會看到編譯鏈路


Inline-info 會顯示哪些方法進行了內聯優化。

這裏看到的OSR,就是常聽到的棧上替換(On-stack replacement),用於優化在解釋器中執行時,向後跳轉的循環分支達到某個閾值時就會被編譯。



JITWatch 還有一個沙箱的環境,能夠用來實驗觀察 JIT的行爲,觀察 JVM 裏JIT的決策過程。


有了工具的幫助,咱們能更好的理解JIT 對應用優化的決策,從而讓應用性能更佳。



若有幫助,幫忙在看轉發支持我一把吧,感謝。


近期文章

五分鐘爆改,把你的JSON/CSV文件打形成MySQL數據庫

JVM:有些內部信息我悄悄告訴你

不用Jar 包的Agent?幾行代碼實現運行時加強?

理解了 1+2的過程,就理解了Java虛擬機

快放開那些搗亂的猴子!

Java七武器系列孔雀翎-- 問題診斷神器BTrace

Java七武器系列長生劍 -- Java虛擬機的顯微鏡 Serviceability Agent

寫代碼效率不高?放過Ctrl C 和 V,讓AI來能幫你寫代碼吧

怎樣閱讀源代碼?




這裏是「Tomcat那些事兒」,關注源碼|實戰|成長等話題,歡迎關注,一塊兒成長。


本文分享自微信公衆號 - Tomcat那些事兒(tomcat0000)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索