您看到的是一些JIT優化的結果應該清楚看到您收到的所有評論。但是真正發生了什麼以及爲什麼代碼在外部for
的迭代次數相同後幾乎被優化?
我會盡量回答這兩個問題,但請記住,這裏解釋的所有內容都是相對於Oracle熱點虛擬機的只有。沒有定義JVM JIT應該如何表現的Java規範。首先,讓我們看看JIT在運行該測試程序時使用一些附加標誌(純JVM足以運行該程序,無需加載調試共享庫,這對於某些UnlockDiagnosticVMOptions
選項是必需的) :
java -XX:+PrintCompilation Test
執行這個輸出完畢(除開頭的幾行,顯示其它方法正在整理):
[...]
195017
184573
184342
184262
183491
189494
131 51% 3 Test::getTimes @ 2 (22 bytes)
245167
132 52 3 Test::getTimes (22 bytes)
165144
65090
132 53 1 java.nio.Buffer::limit (5 bytes)
59427
132 54% 4 Test::getTimes @ 2 (22 bytes)
75137
48110
135 51% 3 Test::getTimes @ -2 (22 bytes) made not entrant
142 55 4 Test::getTimes (22 bytes)
150820
86951
90012
91421
你的代碼中printlns
交錯帶DIAGNOST與JIT正在執行的編譯有關的信息。 尋找在一個單一的線路:
131 51% 3 Test::getTimes @ 2 (22 bytes)
每一列具有以下含義:
- 時間戳
- 編譯ID(與如果需要額外的屬性)
- 分層彙編級
- 方法短名稱(如果有的話,@
osr_bci
)
- 編譯m ethod大小
只保留相關getTimes
行:
131 51% 3 Test::getTimes @ 2 (22 bytes)
132 52 3 Test::getTimes (22 bytes)
132 54% 4 Test::getTimes @ 2 (22 bytes)
135 51% 3 Test::getTimes @ -2 (22 bytes) made not entrant
142 55 4 Test::getTimes (22 bytes)
很明顯,getTimes
正在編譯不止一次,但每次都在以不同的方式編譯。
即%
符號表示堆棧上替換(OSR)已經被執行,這意味着包含在getTimes
的10K環已編譯從方法的其餘部分,該JVM分離置換該第編譯版本的方法代碼。 osr_bci
是一個指向這個新編譯的代碼塊的索引。
下編譯是一個典型的JIT編譯,編譯所有的getTimes
方法(大小仍然是相同的,因爲沒有別的比環路其他該方法)。
第三次執行另一個OSR,但處於不同的分層級別。在Java7中添加了分層編譯,並且基本上允許JVM在運行時選擇客戶端或服務器 JIT模式,在必要時在兩者之間自由切換。客戶端模式執行更簡單的一組優化策略,而服務器模式能夠應用更復雜的優化,另一方面,編譯時花費更大。
我不會去到有關不同模式或約分層編制的詳細信息,如果您需要更多的信息,我由斯科特·奧克斯建議Java Performance: The Definitive Guide並檢查this question解釋水平之間有什麼變化。
回到PrintCompilation的輸出,這裏的要點是,從某個時間點開始,一系列複雜度增加的編譯被執行,直到方法變得明顯穩定(即JIT不再編譯它)。
那麼,爲什麼所有這些都是在主循環5-10次迭代後的某個時間點開始的呢?
因爲內部getTimes
循環已成爲「熱」。
的熱點VM,通常定義了「熱」的那些方法已被調用的至少10K倍(這是歷史默認閾值,可以使用-XX:CompileThreshold=<num>
被改變,以分層彙編現在有多個閾值),但在OSR的例子我猜測它是在代碼塊被認爲「足夠熱」的情況下執行的,就絕對或相對執行時間而言,該方法包含它。
其他參考
PrintCompilation Guide通過克里斯塔爾莫
Java Performance: The Definitive Guide
你能始終如一地重現此問題? – CodeNewbie
我經常這樣做,總會有一點讓時間變得更小。 – MarkusK96
看起來像JIT給我。關於爲什麼你不應該自己對Java進行基準測試,而是使用諸如[jmh](http://openjdk.java.net/projects/code-tools/jmh/)等適當的微型平臺工具的一個非常尖銳的示範。 –