Jit編譯:just in time 編譯. Java代碼只有在執行一段時間之後纔會進行jit編譯。異步
Hotspot會編譯優化那些熱點代碼,以求最大的性能收益。性能
Jit編譯的好處:優化
1. 執行一段時間後,能夠統計出哪些代碼的調用頻次高。線程
2. 執行一段時間後,編譯器能夠得到代碼的一些性能信息,來加大編譯優化的力度。因此,如今的jit編譯優化甚至可能比C語言的編譯優化作的還要效果好。日誌
jit編譯器有倆種:C1(client),和C2(Server) 倆種編譯器。code
C1:編譯的時機要比C2快,編譯的代碼要比C2多。好處了能夠在程序啓動剛開始就能得到比較好的性能。應用啓動時間快。server
C2:編譯的時機要比C1晚,由於它但願能夠得到更多的統計信息,進行優化程度更高的優化處理。應用啓動時間慢。隊列
分層編譯:程序在剛啓動的時候進行C1編譯,隨着代碼執行的時間增長,再慢慢進行C2編譯。內存
應用運行的時間短就採用C1,不然使用C2。通常來講,用分層編譯老是沒錯的。資源
Jit編譯後的字節碼會保存在code-cache中,而code-cache的大小是有限的。使用C1或者分層編譯所編譯的代碼較多,比較容易填滿codecache,而C2編譯的代碼量不會那麼多。能夠經過-XX:ReservedCodeCacheSize=N 來設置code-cache的最大值。須要注意的是,任何內存區均可能進行內存保留,因此把最大值設大,可能會致使佔用過多的內存。也就是由於內存的消耗,因此,咱們須要考慮機器資源來權衡jit編譯的程度,來最大化應用的性能。
JVM有倆個參數:方法調用計數器和循環回邊計數器。
標準編譯:當方法調用計數器和循環回邊計數器記錄的總次數超過必定的閾值,就對一個方法進行jit編譯。
棧上替換:若是方法計數器的值沒有達到閾值,可是循環回邊計數器達到的必定的值,會對這個循環進行編譯,而不會對整個方法進行編譯,並在方法棧上進行替換。
C1和C2在編譯時機上的不一樣主要是因爲這倆個計數器的閾值是不一樣的。另外,計數器的值還會週期性的減小,因此它表示的是最新的調用熱度。jit編譯的閾值是能夠調節的,可是要考慮到調整完後對code-cache帶來的消耗。
對jit編譯狀況進行統計的方法有:
1. 開啓 -XX:+PrintCompilation
2. Jstat -compiler pid
jstat -printcompilation
編譯線程:編譯的操做是異步的,會有編譯線程在後臺對達到要求的代碼進行編譯。線程分爲client線程和server線程,分別用於C1,C2編譯器。分層編譯倆種線程都有。編譯線程的量是可調節的,可是這一般影響的是應用在熱身期的性能。若是過了熱身期,這些編譯線程就不會在佔用cpu了。
內聯:內聯帶來的性能提高是巨大的,一方面是內聯自己帶來的方法調用的減小。另外一個是否重要的方面是,內聯後的代碼,又能夠促進不少其餘優化。內聯的關閉-XX:-Inline (默認是開的)
常規的內聯:當方法很小時會進行內聯。小於35個字節或-XX:MaxInlineSize=N 所設的值。
頻繁調用帶來的內聯:當調用很頻繁時,若是小於325個字節或者-XX:MaxFreqInlineSize=N 所設定的值。
逃逸分析(-XX:+DoEscapeAnalysis,默認爲 true) ,編譯器會作一些很是複雜和激進的優化。好比把一些沒有用到的變量的計算省略掉。
逆優化:指編譯器對一些編譯進行撤回。有兩種逆優化的情形:代碼狀態分別爲「made not entrant」(代碼被丟棄)和「made zombie」(產生殭屍代碼)
「made not entrant」(代碼被丟棄)的發生有倆種狀況:
1. 若是一個方法內部的一個邏輯分支一直被調用,而後進行了jit編譯,若是這時出現了另外一個邏輯分支的調用,就會致使原來的編譯代碼失效,而後被丟棄。在編譯的詳情日誌裏也會出現made not entrant,而後會出現made zombie
2. 在分層編譯中,C1編譯的代碼,接着被C2編譯後,以前的jit編譯的代碼就會被丟棄。
「made zombie」(產生殭屍代碼) :指上面被丟棄的代碼被GC回收了。
分層編譯級別:
C1和C2都有本身的編譯隊列,存儲達到閾值的須要編譯的代碼。若是C2的隊列滿了,則這段代碼會進行2,而後進行C1編譯。等C2隊列空閒後再進行C2編譯。一樣,若是C1編譯隊列滿了,也會進行相似的操做。