Klausa percubaan akhirnya ditentukan dan ditunjukkan

Selamat datang ke ansuran Under The Hood yang lain . Lajur ini memberikan pengembang Java sekilas mengenai mekanisme misteri yang mengklik dan berpusing di bawah program Java mereka yang sedang berjalan. Artikel bulan ini meneruskan perbincangan mengenai set arahan bytecode mesin maya Java (JVM). Fokusnya adalah cara di mana JVM menangani finallyklausa dan kod bytes yang berkaitan dengan klausa ini.

Akhirnya: Sesuatu yang boleh diceriakan

Oleh kerana mesin maya Java menjalankan kod bytes yang mewakili program Java, ia mungkin keluar dari blok kod - pernyataan antara dua pendakap keriting yang sepadan - dengan salah satu daripada beberapa cara. Untuk satu, JVM hanya dapat melakukan pelepasan kurungan penutup kod penutup. Atau, mungkin terdapat pernyataan putus, terus, atau kembali yang menyebabkannya melompat keluar dari blok kod dari suatu tempat di tengah blok. Akhirnya, pengecualian dapat dilemparkan yang menyebabkan JVM melompat ke klausa tangkapan yang sesuai, atau, jika tidak ada klausa tangkapan yang sesuai, untuk menghentikan utas. Dengan adanya titik keluar berpotensi ini dalam satu blok kod, adalah wajar untuk mempunyai cara mudah untuk menyatakan bahawa sesuatu berlaku tidak kira bagaimana sekatan kod keluar. Di Jawa, keinginan seperti itu dinyatakan dengantry-finally fasal.

Untuk menggunakan try-finallyklausa:

  • sertakan dalam tryblok kod yang mempunyai beberapa titik keluar, dan

  • masukkan finallyblok kod yang mesti berlaku tidak kira bagaimana tryblok itu keluar.

Sebagai contoh:

cubalah {// Blok kod dengan beberapa titik keluar} akhirnya {// Blok kod yang selalu dilaksanakan ketika blok percubaan keluar, // tidak kira bagaimana blok percubaan keluar} 

Sekiranya anda mempunyai catchklausa yang berkaitan dengan tryblok, anda mesti memasukkan finallyklausa setelah semua catchklausa, seperti dalam:

cuba {// Blok kod dengan beberapa titik keluar} tangkapan (Sejuk e) {System.out.println ("Tertangkap sejuk!"); } tangkapan (APopFly e) {System.out.println ("Tertangkap lalat!"); } tangkap (SomeonesEye e) {System.out.println ("Tertangkap mata seseorang!"); } akhirnya {// Blok kod yang selalu dijalankan ketika blok percubaan keluar, // tidak kira bagaimana blok percubaan keluar. System.out.println ("Adakah itu sesuatu yang boleh diceriakan?"); }

Sekiranya semasa pelaksanaan kod dalam tryblok, pengecualian dilemparkan yang ditangani oleh catchklausa yang terkait dengan tryblok, finallyklausa akan dilaksanakan setelah catchklausa. Sebagai contoh, jika Coldpengecualian dilemparkan semasa pelaksanaan penyataan (tidak ditunjukkan) di tryblok di atas, teks berikut akan ditulis ke output standard:

Tertangkap sejuk! Adakah itu sesuatu yang boleh diceriakan?

Klausa percubaan akhirnya dalam kod bytek

Dalam kod bytek, finallyklausa bertindak sebagai subrutin miniatur dalam satu kaedah. Pada setiap titik keluar di dalam tryblok dan catchklausa yang berkaitan , subrutin miniatur yang sesuai dengan finallyklausa disebut. Setelah finallyklausa selesai - selagi ia selesai dengan melaksanakan melewati pernyataan terakhir dalam finallyklausa itu, bukan dengan membuang pengecualian atau melaksanakan pengembalian, teruskan, atau putus - subrutin miniatur itu sendiri kembali. Pelaksanaan berterusan setelah titik subrutin mini dipanggil di tempat pertama, sehingga tryblok dapat keluar dengan cara yang sesuai.

Opcode yang menyebabkan JVM melompat ke subrutin miniatur adalah arahan jsr . The jsr arahan mengambil operan dua bait, mengimbangi daripada lokasi jsr arahan mana subrutin kecil bermula. Varian kedua arahan jsr adalah jsr_w , yang melakukan fungsi yang sama dengan jsr tetapi memerlukan operan yang luas (empat byte). Apabila JVM pertemuan jsr atau jsr_w arahan, ia menolak alamat kembali ke dalam tindanan, kemudian terus pelaksanaan pada permulaan subrutin miniatur. Alamat pengembalian adalah pengimbangan kod bytek yang segera mengikuti jsr atauarahan jsr_w dan operannya .

Setelah subrutin miniatur selesai, ia memanggil arahan ret , yang kembali dari subrutin. The ret arahan mengambil masa satu operan, indeks ke dalam pembolehubah tempatan di mana alamat kembali disimpan. Opkod yang menangani finallyklausa diringkaskan dalam jadual berikut:

Akhirnya fasal
Kod Op Operan Penerangan
jsr branchbyte1, branchbyte2 menolak alamat pengembalian, cawangan untuk mengimbangi
jsr_w branchbyte1, branchbyte2, branchbyte3, branchbyte4 menolak alamat pengembalian, cawangan ke ofset lebar
ret indeks kembali ke alamat yang disimpan dalam indeks pemboleh ubah tempatan

Jangan mengelirukan subrutin miniatur dengan kaedah Java. Kaedah Java menggunakan set arahan yang berbeza. Arahan seperti invokevirtual atau invokenonvirtual menyebabkan kaedah Java dipanggil, dan arahan seperti return , areturn , atau ireturn menyebabkan kaedah Java kembali. The jsr arahan tidak menyebabkan kaedah Java yang akan dilaksanakan. Sebaliknya, ia menyebabkan terjadinya kod op yang berbeza dengan kaedah yang sama. Begitu juga, arahan ret tidak kembali dari kaedah; sebaliknya, ia kembali ke opcode dengan kaedah yang sama yang segera mengikuti arahan jsr panggilan dan operandanya . Kod byte yang melaksanakan afinallyklausa disebut subrutin kecil kerana mereka bertindak seperti subrutin kecil dalam aliran bytecode dari satu kaedah.

Anda mungkin berfikir bahawa instruksi ret harus memasukkan alamat kembali dari tumpukan, kerana di sinilah ia didorong oleh arahan jsr . Tetapi tidak. Sebaliknya, pada permulaan setiap subrutin, alamat pengembalian muncul dari bahagian atas timbunan dan disimpan dalam pemboleh ubah tempatan - pemboleh ubah tempatan yang sama dari mana arahan ret kemudian mendapatkannya. Ini cara simetri bekerja dengan alamat kembali itu perlu kerana akhirnya fasal (dan oleh itu, subrutin mini) sendiri boleh membuang pengecualian atau memasukkan return, breakatau continuekenyataan. Kerana kemungkinan ini, alamat pengembalian tambahan yang didorong ke tumpukan oleh jsrarahan mesti dikeluarkan dari timbunan dengan segera, supaya ia tidak akan masih berada di sana jika finallykeluar fasal dengan break, continue, returnatau pengecualian dibuang. Oleh itu, alamat kembali disimpan ke pemboleh ubah tempatan pada permulaan finallysubrutin mini klausa.

Sebagai gambaran, pertimbangkan kod berikut, yang merangkumi finallyklausa yang keluar dengan pernyataan putus. Hasil kod ini adalah, tanpa mengira parameter bVal yang diteruskan ke kaedah surpriseTheProgrammer(), kaedah tersebut mengembalikan false:

surprise boolean staticTheProgrammer (boolean bVal) {sambil (bVal) {cuba {kembali benar; } akhirnya {rehat; }} kembali palsu; }

Contoh di atas menunjukkan mengapa alamat pengembalian mesti disimpan ke dalam pemboleh ubah tempatan pada awal finallyklausa. Kerana finallyklausa keluar dengan jeda, tidak pernah melaksanakan arahan ret . Akibatnya, JVM tidak pernah mundur untuk menyelesaikan return truepenyataan "". Sebagai gantinya, ia hanya meneruskan dengan breakdan turun melewati pendakap keriting penutupan whilepenyataan. Pernyataan seterusnya adalah " return false," yang betul-betul dilakukan oleh JVM.

Tingkah laku yang ditunjukkan oleh finallyklausa yang keluar dengan a breakjuga ditunjukkan oleh finallyklausa yang keluar dengan returnatau continue, atau dengan membuang pengecualian. Sekiranya finallyklausa keluar untuk mana-mana sebab ini, arahan ret pada akhir finallyklausa tidak pernah dilaksanakan. Kerana arahan ret tidak dijamin akan dilaksanakan, tidak dapat diandalkan untuk menghapus alamat kembali dari tumpukan. Oleh itu, alamat kembali disimpan ke pemboleh ubah tempatan pada awal finallysubrutin mini klausa.

Sebagai contoh lengkap, pertimbangkan kaedah berikut, yang mengandungi tryblok dengan dua titik keluar. Dalam contoh ini, kedua-dua titik keluar adalah returnpernyataan:

stat stat int giveMeThatOldFashionedBoolean (boolean bVal) {cuba {if (bVal) {return 1; } pulangkan 0; } akhirnya {System.out.println ("Sudah lama."); }}

Kaedah di atas menyusun kod byk berikut:

// Urutan bytecode untuk blok percubaan: 0 iload_0 // Tolak pemboleh ubah tempatan 0 (arg lulus sebagai pembahagi) 1 ifeq 11 // Tolak pemboleh ubah tempatan 1 (arg lulus sebagai dividen) 4 iconst_1 // Push int 1 5 istore_3 // Muncul int (the 1), simpan ke pemboleh ubah tempatan 3 6 jsr 24 // Lompat ke subrutin mini untuk klausa akhirnya 9 iload_3 // Tolak pemboleh ubah tempatan 3 (1) 10 ireturn // Kembali int di atas susun (1) 11 iconst_0 // Tekan int 0 12 istore_3 // Pop int (the 0), simpan ke pemboleh ubah tempatan 3 13 jsr 24 // Lompat ke subrutin mini untuk klausa akhirnya 16 iload_3 // Tolak tempatan pemboleh ubah 3 (the 0) 17 ireturn // Return int di atas timbunan (the 0) // Urutan bytecode untuk klausa tangkapan yang menangkap apa-apa pengecualian // dilemparkan dari dalam blok percubaan. 18 astore_1 // Pop rujukan ke pengecualian yang dilemparkan,simpan // ke pemboleh ubah tempatan 1 19 jsr 24 // Lompat ke subrutin mini untuk klausa 22 aload_1 // // Tolak rujukan (ke pengecualian yang dilemparkan) dari // pemboleh ubah tempatan 1 23 athrow // Rethrow pengecualian yang sama / Subrutin miniatur yang menerapkan blok akhirnya. 24 astore_2 // Pop alamat pengembalian, simpan dalam pemboleh ubah tempatan 2 25 getstatic # 8 // Dapatkan rujukan ke java.lang.System.out 28 ldc # 1 // Tolak dari kolam berterusan 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Alamat Return to Return yang disimpan dalam pemboleh ubah tempatan 2simpan dalam pemboleh ubah tempatan 2 25 getstatic # 8 // Dapatkan rujukan ke java.lang.System.out 28 ldc # 1 // Tolak dari kolam berterusan 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Alamat kembali ke alamat yang disimpan dalam pemboleh ubah tempatan 2simpan dalam pemboleh ubah tempatan 2 25 getstatic # 8 // Dapatkan rujukan ke java.lang.System.out 28 ldc # 1 // Tolak dari kolam berterusan 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Alamat kembali ke alamat yang disimpan dalam pemboleh ubah tempatan 2

Kod byk untuk tryblok merangkumi dua arahan jsr . Arahan jsr lain terdapat dalam catchklausa. The catchfasal ditambah oleh pengkompil kerana jika pengecualian dibuang semasa pelaksanaan daripada tryblok, akhirnya blok masih perlu dilaksanakan. Oleh itu, catchklausa hanya memanggil subrutin miniatur yang mewakili finallyklausa, kemudian melontarkan pengecualian yang sama sekali lagi. Jadual pengecualian untuk giveMeThatOldFashionedBoolean()kaedah, ditunjukkan di bawah, menunjukkan bahawa pengecualian yang dilemparkan antara dan termasuk alamat 0 dan 17 (semua kod byte yang melaksanakan tryblok) dikendalikan oleh catchklausa yang bermula di alamat 18.

Jadual pengecualian: dari jenis sasaran 0 18 18 sebarang 

Bytecode finallyklausa bermula dengan memasukkan alamat kembali dari timbunan dan menyimpannya ke pemboleh ubah tempatan dua. Pada akhir finallyklausa, arahan ret mengambil alamat pengembaliannya dari tempat yang betul, pemboleh ubah tempatan dua.

HopAround: Simulasi mesin maya Java

Applet di bawah menunjukkan mesin maya Java yang menjalankan urutan kod bytek. Urutan bytecode dalam simulasi dihasilkan oleh javacpenyusun untuk hopAround()kaedah kelas yang ditunjukkan di bawah:

kelas Clown {static int hopAround () {int i = 0; sementara (benar) {cuba {cuba {i = 1; } akhirnya {// klausa akhirnya yang pertama i = 2; } i = 3; pulangan i; // ini tidak pernah selesai, kerana terus} akhirnya {// kedua akhirnya klausa jika (i == 3) {teruskan; // ini terus menggantikan penyata pengembalian}}}}}

The bytecodes dihasilkan oleh javacuntuk hopAround()kaedah adalah seperti berikut: