次のコードは、
public class TestFastThrow {
public static void main(String[] args) {
int count = 0;
int exceptionStackTraceSize = 0;
Exception exception = null;
do {
try {
throwsNPE(1);
}
catch (Exception e) {
exception = e;
if (exception.getStackTrace().length != 0) {
exceptionStackTraceSize = exception.getStackTrace().length;
count++;
}
}
}
while (exception.getStackTrace().length != 0);
System.out.println("Iterations to fastThrow :" + count + ", StackTraceSize :" + exceptionStackTraceSize);
}
static void throwsNPE(int callStackLength) {
throwsNPE(callStackLength, 0);
}
static void throwsNPE(int callStackLength, int count) {
if (count == callStackLength) {
((Object) null).getClass();
}
else {
throwsNPE(callStackLength, count + 1);
}
}
}
複数回実行した後、次の出力が得られます、
Iterations to fastThrow :5517, StackTraceSize :4
Iterations to fastThrow :2825, StackTraceSize :5
Iterations to fastThrow :471033, StackTraceSize :6
Iterations to fastThrow :1731, StackTraceSize :7
Iterations to fastThrow :157094, StackTraceSize :10
.
.
.
Iterations to fastThrow :64587, StackTraceSize :20
Iterations to fastThrow :578, StackTraceSize :29
VMの詳細
Java HotSpot(TM) 64-Bit Server VM (11.0.5+10-LTS) for bsd-amd64 JRE (11.0.5+10-LTS)
-XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation -XX:+PrintAssembly
何を驚くべきものであったが、スタックトレースが偶数の長さであれば、なぜJITを最適化するために、より多くの反復を取るんでしょうか?
私は、JITログを有効にしてjitwatchを介して分析が、ちょうどC1とC2コンパイルもサイズのスタックトレースのために、後に起こっているように見えるときのタイムラインという、有用何も表示されませんでした。
タイムラインは、このようなものである、(ときを見てjava.lang.Throwable.getStackTrace()
コンパイルされています)
| StackSize | 10 | 11 |
|---------------|-------|-------|
| Queued for C1 | 1.099 | 1.012 |
| C1 | 1.318 | 1.162 |
| Queued for C2 | 1.446 | 1.192 |
| C2 | 1.495 | 1.325 |
なぜ、まさにこの出来事はありますか?そして、何ヒューリスティックは速い投球のためのJITの使用していますか?
この効果は、トリッキーの結果である階層型コンパイルとインライン化方針。
私は簡単な例で説明しましょう:
public class TestFastThrow {
public static void main(String[] args) {
for (int iteration = 0; ; iteration++) {
try {
throwsNPE(2);
} catch (Exception e) {
if (e.getStackTrace().length == 0) {
System.out.println("Iterations to fastThrow: " + iteration);
break;
}
}
}
}
static void throwsNPE(int depth) {
if (depth <= 1) {
((Object) null).getClass();
}
throwsNPE(depth - 1);
}
}
簡単にするために、私は除いて、コンパイルからのすべてのメソッドを除外しますthrowsNPE
。
-XX:CompileCommand=compileonly,TestFastThrow::throwsNPE -XX:+PrintCompilation
ホットスポットは、デフォルトでは階層型コンパイルを使用しています。ここで
throwsNPE
第一層3(プロファイリングとC1)でコンパイルされます。C1にプロファイリングするC2によって後者の方法を再コンパイルすることが可能となります。OmitStackTraceInFastThrow
最適化は、C2のみコンパイルされたコードで動作します。だから、早くコードがC2でコンパイルされた - 以下の反復は、ループが終了する前に通過します。どのようにプロファイリングC1-コンパイルされたコードの作品に:カウンタは、すべてのメソッド呼び出しで、すべての後方分岐にインクリメントされ(ただし、には後方の支店がない
throwsNPE
方法)。カウンタは、特定の設定可能なしきい値に達したときに、JVMコンパイルポリシーは、現在の方法は、再コンパイルする必要があるかどうかを決定します。throwsNPE
再帰的な方法です。ホットスポットは、最大再帰呼び出しをインライン化することができます-XX:MaxRecursiveInlineLevel
(デフォルト値は1です)。C1コンパイルされたコードの呼び出しは、JVMのコンパイルポリシーにバックアップする頻度頻度、インライン化呼び出し対通常の呼び出しのために異なります。通常の方法を通知は、すべて2つのJVM 10の呼び出し(
-XX:Tier3InvokeNotifyFreqLog=10
すべての2:はるかにまれJVMに通知するインライン方式しばらく)、20呼び出しを(-XX:Tier23InlineeNotifyFreqLog=20
)。再帰呼び出しの数が偶数の場合は、すべての呼び出しが続く
Tier23InlineeNotifyFreqLog
パラメータを。呼び出しの数が奇数の場合、インライン化は、最後の残りの呼び出しのためではない仕事をしており、この最後の呼び出しは、以下のTier3InvokeNotifyFreqLog
パラメータを。コールの深さが偶数の場合、この手段は、
throwsNPE
後にのみ2つの再コンパイルされます20 2の後、すなわち通話を19ループの反復。それはあなたが上記のコードを実行したときに表示されます正確に何ですthrowNPE(2)
:Iterations to fastThrow: 524536
524536は、2に非常に近い19 = 524288
あなたと同じアプリケーションを実行する場合さて、
-XX:Tier23InlineeNotifyFreqLog=15
反復回数は2に近いだろう14 = 16384。Iterations to fastThrow: 16612
今度は、呼び出すためのコードを変更してみましょう
throwsNPE(1)
。プログラムは関係なく、非常に迅速に終了しますTier23InlineeNotifyFreqLog
値。今別のオプションルールだからです。私は、プログラムを再実行した場合でも-XX:Tier3InvokeNotifyFreqLog=20
、ループが2後より早くない仕上がりになる20回の反復:Iterations to fastThrow: 1048994
概要
高速スロー最適化は唯一のC2-コンパイルされたコードに適用されます。インライン化の1つのレベル(に-XX:MaxRecursiveInlineLevel
)、C2のコンパイルがトリガされる以前(2つの後Tier3InvokeNotifyFreqLogの呼び出し、再帰呼び出しの数が奇数の場合)後、又は(2つの後Tier23InlineeNotifyFreqLogを呼び出し、すべての再帰呼び出しをインライン化することによって覆われている場合)。