Try-finally句が定義され、実証されました

Under TheHoodの別の記事へようこそ。このコラムでは、Java開発者に、実行中のJavaプログラムの下でクリックしたり回転したりする不思議なメカニズムを垣間見ることができます。今月の記事では、Java仮想マシン(JVM)のバイトコード命令セットについて説明します。その焦点は、JVMがfinally句とこれらの句に関連するバイトコードを処理する方法です。

最後に:応援する何か

Java仮想マシンは、Javaプログラムを表すバイトコードを実行するときに、いくつかの方法の1つで、コードのブロック(2つの一致する中括弧の間のステートメント)を終了する場合があります。1つは、JVMは、コードブロックの閉じ中括弧を超えて実行できることです。または、break、continue、またはreturnステートメントが発生して、ブロックの中央のどこかからコードのブロックから飛び出す可能性があります。最後に、JVMが一致するcatch句にジャンプするか、一致するcatch句がない場合はスレッドを終了する例外がスローされる可能性があります。これらの潜在的な出口点が単一のコードブロック内に存在するため、コードブロックがどのように終了しても、何かが起こったことを簡単に表現できることが望ましいです。Javaでは、そのような欲求はtry-finally 句。

try-finally句を使用するには:

  • try複数の出口点を持つコードをブロックで囲み、

  • ブロックがfinallyどのようにtry終了しても発生しなければならないコードをブロックに入れます。

例えば:

try {//複数の出口点を持つコードのブロック} finally {// tryブロックが終了したときに常に実行されるコードのブロック// tryブロックがどのように終了したかに関係なく} 

ブロックにcatch関連付けられている句がある場合は、次のように、すべての句の後tryfinally句を配置する必要がありますcatch

try {//複数の出口点を持つコードのブロック} catch(Cold e){System.out.println( "Caught cold!"); } catch(APopFly e){System.out.println( "Caught a pop fly!"); } catch(SomeonesEye e){System.out.println( "誰かの目を捕まえた!"); } final {// tryブロックがどのように終了しても、// tryブロックが終了したときに常に実行されるコードのブロック。 System.out.println( "それは応援するものですか?"); }

tryブロック内のコードの実行中に、ブロックにcatch関連付けられた句によって処理される例外がスローされた場合try、そのfinally句はcatch句の後に実行されます。たとえば、上記Coldtryブロックのステートメント(図には示されていません)の実行中に例外がスローされた場合、次のテキストが標準出力に書き込まれます。

風邪をひいた!それは応援するものですか?

バイトコードのtry-finally句

バイトコードでは、finally句はメソッド内のミニチュアサブルーチンとして機能します。tryブロック内の各出口点とそれに関連するcatch節で、節に対応するミニチュアサブルーチンfinallyが呼び出されます。finally句が完了した後(finally例外をスローしたり、return、continue、またはbreakを実行したりするのではなく、句の最後のステートメントを超えて実行して完了する限り)、ミニチュアサブルーチン自体が戻ります。ミニチュアサブルーチンが最初に呼び出された時点を過ぎても実行が継続されるためtry、適切な方法でブロックを終了できます。

JVMをミニチュアサブルーチンにジャンプさせるオペコードはjsr命令です。JSR命令は、2バイト・オペランドの位置からのオフセットかかりJSR小型サブルーチンが始まる命令。jsr命令の2番目のバリアントはjsr_wです。これは、jsrと同じ機能を実行しますが、ワイド(4バイト)オペランドを取ります。 JVMがjsrまたはjsr_w命令を検出すると、JVMはリターンアドレスをスタックにプッシュし、ミニチュアサブルーチンの開始時に実行を続行します。戻りアドレスは、jsrまたはの直後のバイトコードのオフセットです。jsr_w命令とそのオペランド。

ミニチュアサブルーチンが完了すると、サブルーチンから戻るret命令が呼び出されます。RET命令は1つのオペランド、リターンアドレスが格納されているローカル変数へのインデックスをとります。finally句を処理するオペコードは、次の表に要約されています。

最後に節
オペコード オペランド 説明
jsr branchbyte1、branchbyte2 リターンアドレスをプッシュし、分岐してオフセットします
jsr_w branchbyte1、branchbyte2、branchbyte3、branchbyte4 リターンアドレスをプッシュし、ワイドオフセットに分岐します
ret インデックス ローカル変数インデックスに格納されているアドレスに戻ります

ミニチュアサブルーチンをJavaメソッドと混同しないでください。 Javaメソッドは、異なる命令セットを使用します。invokevirtualinvokenonvirtualなどの命令により、Javaメソッドが呼び出され、returnareturn、またはireturnなどの命令により、Javaメソッドが返されます。JSR命令は、Javaメソッドが呼び出されることはありません。代わりに、同じメソッド内の別のオペコードにジャンプします。同様に、ret命令はメソッドから返されません。むしろ、呼び出し元のjsr命令とそのオペランドの直後に続く同じメソッドでオペコードに戻ります。を実装するバイトコードfinally句は、単一のメソッドのバイトコードストリーム内の小さなサブルーチンのように機能するため、ミニチュアサブルーチンと呼ばれます。

ret命令は、jsr命令によってプッシュされた場所であるため、リターンアドレスをスタックからポップする必要があると考えるかもしれません。しかし、そうではありません。代わりに、各サブルーチンの開始時に、戻りアドレスがスタックの最上位からポップされ、ローカル変数(ret命令が後で取得するのと同じローカル変数)に格納されます。 finally節(したがって、ミニチュアサブルーチン)自体が例外をスローするか、含めることができるので、リターンアドレスでの作業のこの非対称には必要であるreturnbreakまたはcontinue文。この可能性があるため、jsrによってスタックにプッシュされた追加のリターンアドレスあれば、それはまだ存在しないよう命令は、すぐにスタックから削除されなければならないfinallyとの条項が終了breakcontinuereturn、またはスローされた例外。したがって、戻りアドレスは、任意のfinally句のミニチュアサブルーチンの開始時にローカル変数に格納されます。

例として、finallybreakステートメントで終了する句を含む次のコードについて考えてみます。このコードの結果はsurpriseTheProgrammer()、メソッドに渡されたパラメーターbValに関係なく、メソッドは以下を返しますfalse

static boolean summaryTheProgrammer(boolean bVal){while(bVal){try {return true; }最後に{ブレーク; }} falseを返します。}

上記の例は、戻りアドレスをfinally句の先頭でローカル変数に格納する必要がある理由を示しています。finally句はブレークで終了するため、ret命令を実行することはありません。その結果、JVMは " return true"ステートメントを終了するために戻ることはありません。代わりに、それはただ先に進みbreakwhileステートメントの最後の中括弧を過ぎてドロップダウンします。次のステートメントは「return false、」です。これはまさにJVMが行うことです。

afinallyで終了する句によって示される動作breakfinallyreturnまたはcontinueで終了する句によって、または例外をスローすることによっても示されます。finallyこれらの理由のいずれかで句が終了した場合、句の最後にあるret命令finallyは実行されません。ret命令は実行が保証されていないため、スタックからリターンアドレスを削除するために信頼することはできません。したがって、戻りアドレスは、finally句のミニチュアサブルーチンの先頭でローカル変数に格納されます。

完全な例として、try2つの出口点を持つブロックを含む次のメソッドについて考えてみます。この例では、両方の出口点はreturnステートメントです。

static int giveMeThatOldFashionedBoolean(boolean bVal){try {if(bVal){return 1; } 0を返します。}最後に{System.out.println( "昔ながらの。"); }}

上記のメソッドは、次のバイトコードにコンパイルされます。

// tryブロックのバイトコードシーケンス:0 iload_0 //ローカル変数0をプッシュ(引数は除数として渡されます)1 ifeq 11 //ローカル変数1をプッシュします(引数は被除数として渡されます)4 iconst_1 // int1をプッシュします5istore_3 // int(1)をポップし、ローカル変数に格納します3 6 jsr 24 // finally句のミニサブルーチンにジャンプします9iload_3 //ローカル変数3(1)をプッシュします10 ireturn // intをstack(1)11 iconst_0 // int0をプッシュ12istore_3 // int(0)をポップし、ローカル変数に格納3 13 jsr 24 // finally句のミニサブルーチンにジャンプ16iload_3 //ローカルをプッシュ変数3(0)17 ireturn //スタックの最上位にintを返します(0)// tryブロック内からスローされたあらゆる種類の例外をキャッチするcatch句のバイトコードシーケンス//。 18 astore_1 //スローされた例外への参照をポップします。store //ローカル変数に119 jsr 24 // finally節のミニサブルーチンにジャンプ22aload_1 //ローカル変数から(スローされた例外への)参照をプッシュします1 23 athrow //同じ例外を再スローします/ / finallyブロックを実装するミニチュアサブルーチン。 24 astore_2 //戻りアドレスをポップし、ローカル変数に格納します2 25 getstatic#8 //java.lang.System.outへの参照を取得します28ldc#1 //定数プールからプッシュします30invokevirtual#7 //呼び出しますSystem.out.println()33 ret 2 //ローカル変数2に格納されているリターンアドレスに戻るローカル変数に格納します225 getstatic#8 //java.lang.System.outへの参照を取得します28ldc#1 //定数プールからプッシュします30invokevirtual#7 // System.out.println()を呼び出します33 ret 2 //ローカル変数2に格納されているリターンアドレスに戻るローカル変数に格納します225 getstatic#8 //java.lang.System.outへの参照を取得します28ldc#1 //定数プールからプッシュします30invokevirtual#7 // System.out.println()を呼び出します33 ret 2 //ローカル変数2に格納されているリターンアドレスに戻る

tryブロックのバイトコードには、2つのjsr命令が含まれています。別のjsr命令がcatch句に含まれています。ブロックcatchの実行中に例外がスローされた場合tryでも、finallyブロックを実行する必要があるため、この句はコンパイラによって追加されます。したがって、catch句は、句を表すミニチュアサブルーチンを呼び出すだけでfinally、同じ例外を再度スローします。giveMeThatOldFashionedBoolean()以下に示すメソッドの例外テーブルは、アドレス0と17(tryブロックを実装するすべてのバイトコード)の間でスローされた例外catchが、アドレス18で始まる句によって処理されることを示しています。

例外テーブル:fromからtarget type 0 18 18 any 

The bytecodes of the finally clause begin by popping the return address off the stack and storing it into local variable two. At the end of the finally clause, the ret instruction takes its return address from the proper place, local variable two.

HopAround: A Java virtual machine simulation

The applet below demonstrates a Java virtual machine executing a sequence of bytecodes. The bytecode sequence in the simulation was generated by the javac compiler for the hopAround() method of the class shown below:

class Clown {static int hopAround(){int i = 0; while(true){try {try {i = 1; } Final {//最初のfinally節i = 2; } i = 3; iを返します。// continueのためにこれは決して完了しません} finally {// 2番目のfinally節if(i == 3){continue; //このcontinueはreturnステートメントをオーバーライドします}}}}}}

メソッドjavac用に生成されたバイトコードをhopAround()以下に示します。