Velkommen til en anden rate af Under kølerhjelmen. Denne kolonne giver Java-udviklere et glimt af de mystiske mekanismer, der klikker og hvirvler under deres kørende Java-programmer. Denne måneds artikel fortsætter diskussionen af bytecode-instruktionssættet på den virtuelle Java-maskine (JVM). Dens fokus er den måde, hvorpå JVM håndterer langt om længe
klausuler og de bytekoder, der er relevante for disse klausuler.
Endelig: Noget at heje på
Da den virtuelle Java-maskine udfører bytekoder, der repræsenterer et Java-program, kan den muligvis afslutte en kodeblok - udsagnene mellem to matchende krøllede seler - på en af flere måder. For det første kunne JVM simpelthen udføre forbi den lukkede krøllede afstivning af kodeblokken. Eller det kan støde på en pause, fortsætte eller returnere erklæring, der får det til at springe ud af kodeblokken et eller andet sted midt i blokken. Endelig kan en undtagelse kastes, der får JVM til enten at hoppe til en matchende fangstklausul eller, hvis der ikke er en matchende fangstklausul, at afslutte tråden. Da disse potentielle udgangspunkter findes inden for en enkelt kodeblok, er det ønskeligt at have en nem måde at udtrykke, at der skete noget, uanset hvordan en blok med kode afsluttes. I Java udtrykkes et sådant ønske med en prøv endelig
klausul.
At bruge en prøv endelig
klausul:
vedlægge en
prøve
blokere koden, der har flere udgangspunkter, oglæg i en
langt om længe
blokere den kode, der skal ske, uanset hvordanprøve
blok er afsluttet.
For eksempel:
prøv {// Kodeblok med flere udgangspunkter} endelig {// Kodeblok, der altid udføres, når prøveblokken afsluttes, // uanset hvordan prøveblokken afsluttes}
Hvis du har nogen fangst
klausuler forbundet med prøve
blok, skal du sætte langt om længe
klausul efter alle fangst
klausuler som i:
prøv {// Kodeblok med flere udgangspunkter} fangst (kold e) {System.out.println ("Fanget koldt!"); } fange (APopFly e) {System.out.println ("Fanget en popflyvning!"); } fange (SomeonesEye e) {System.out.println ("Fanget nogens øje!"); } endelig {// Kodeblok, der altid udføres, når prøveblokken afsluttes, // uanset hvordan prøveblokken afsluttes. System.out.println ("Er det noget at juble for?"); }
Hvis der under udførelse af koden inden for en prøve
blok, kastes en undtagelse, der håndteres af en fangst
klausul forbundet med prøve
blokere, den langt om længe
klausul udføres efter fangst
klausul. For eksempel, hvis en Kold
undtagelse kastes under udførelse af udsagnene (ikke vist) i prøve
ovenfor, ville følgende tekst blive skrevet til standardoutputtet:
Fanget koldt! Er det noget at heje på?
Prøv endelig klausuler i bytekoder
I bytekoder, langt om længe
klausuler fungerer som miniaturesubrutiner inden for en metode. Ved hvert udgangssted inde i a prøve
blok og dens tilknyttede fangst
klausuler, den miniature subrutine, der svarer til langt om længe
klausul kaldes. Efter langt om længe
klausul afsluttes - så længe den fuldføres ved at udføre forbi den sidste sætning i langt om længe
klausul, ikke ved at kaste en undtagelse eller udføre en retur, fortsætte eller bryde - selve miniaturens underrutine vender tilbage. Udførelsen fortsætter lige forbi det punkt, hvor miniaturesubrutinen blev kaldt i første omgang, så prøve
blok kan afsluttes på passende måde.
Opkoden, der får JVM til at hoppe til en mini-subrutine, er jsr instruktion. Det jsr instruktion tager en to-byte operand, forskydningen fra placeringen af jsr instruktion, hvor miniaturesubrutinen begynder. En anden variant af jsr instruktion er jsr_w, som udfører den samme funktion som jsr men tager en bred (fire-byte) operand. Når JVM møder en jsr eller jsr_w instruktion, det skubber en returadresse på stakken og fortsætter derefter udførelsen i starten af miniaturens underrutine. Returadressen er forskydningen af bytekoden umiddelbart efter jsr eller jsr_w instruktion og dens operander.
Når en miniaturesubrutine er afsluttet, påberåber den sig ret instruktion, som vender tilbage fra subrutinen. Det ret instruktion tager en operand, et indeks i de lokale variabler, hvor returadressen er gemt. Opkoderne, der beskæftiger sig med langt om længe
klausuler er opsummeret i følgende tabel:
Opkode | Operand (er) | Beskrivelse |
---|---|---|
jsr | branchbyte1, branchbyte2 | skubber returadressen, forgrener sig til offset |
jsr_w | branchbyte1, branchbyte2, branchbyte3, branchbyte4 | skubber returadressen, forgrener sig til bred offset |
ret | indeks | vender tilbage til den adresse, der er gemt i det lokale variabelindeks |
Forveks ikke en mini-subrutine med en Java-metode. Java-metoder bruger et andet sæt instruktioner. Instruktioner som f.eks invokevirtual eller invokenonvirtual forårsage, at en Java-metode påberåbes, og instruktioner som f.eks Vend tilbage, areturn, eller ireturn få en Java-metode til at vende tilbage. Det jsr instruktion medfører ikke, at en Java-metode påberåbes. I stedet forårsager det et spring til en anden opcode inden for samme metode. Ligeledes ret instruktion vender ikke tilbage fra en metode; snarere vender det tilbage til opkoden i den samme metode, der straks følger opkaldet jsr instruktion og dens operander. Bykoderne, der implementerer en langt om længe
klausul kaldes en miniature subrutine, fordi de fungerer som en lille subrutine inden for bytecode-strømmen af en enkelt metode.
Du tror måske, at ret instruktion skal pope returadressen fra stakken, for det er her, den blev skubbet af jsr instruktion. Men det gør det ikke. I stedet for, ved starten af hver underrutine, returneres returadressen fra toppen af stakken og lagres i en lokal variabel - den samme lokale variabel, hvorfra ret instruktion senere får det. Denne asymmetriske måde at arbejde med returadressen på er nødvendig, fordi endelig klausuler (og derfor miniature subrutiner) selv kan kaste undtagelser eller inkludere Vend tilbage
, pause
, eller Blive ved
udsagn. På grund af denne mulighed blev den ekstra returadresse, der blev skubbet på stakken af jsr instruktion skal fjernes fra stakken med det samme, så den vil ikke stadig være der, hvis langt om længe
klausul udgår med en pause
, Blive ved
, Vend tilbage
eller kastet undtagelse. Derfor lagres returadressen i en lokal variabel i starten af en hvilken som helst langt om længe
klausulens miniature subrutine.
Som en illustration skal du overveje følgende kode, som inkluderer en langt om længe
klausul, der afsluttes med en pauseopgørelse. Resultatet af denne kode er, at uanset parameteren bVal videregivet til metoden surpriseTheProgrammer ()
, metoden vender tilbage falsk
:
statisk boolsk overraskelseTheProgrammer (boolsk bVal) {mens (bVal) {prøv {returner sand; } endelig {pause; }} returner falsk; }
Eksemplet ovenfor viser, hvorfor returadressen skal gemmes i en lokal variabel i begyndelsen af langt om længe
klausul. Fordi langt om længe
klausul udgår med en pause, den udfører aldrig ret instruktion. Som et resultat går JVM aldrig tilbage for at afslutte "returner sandt
"erklæring. I stedet fortsætter det bare med pause
og falder ned forbi den lukkende krøllede bøjle af mens
udmelding. Den næste erklæring er "returner falsk
, "hvilket er netop hvad JVM gør.
Den adfærd, som a langt om længe
klausul, der udløber med en pause
er også vist af langt om længe
klausuler, der går ud med en Vend tilbage
eller Blive ved
eller ved at kaste en undtagelse. Hvis en langt om længe
klausul udløber af en af disse grunde, ret instruktion i slutningen af langt om længe
klausul udføres aldrig. Fordi ret instruktioner garanteres ikke at blive udført, det kan ikke stole på at fjerne returadressen fra stakken. Derfor lagres returadressen i en lokal variabel i begyndelsen af langt om længe
klausulens miniature subrutine.
For et komplet eksempel, overvej følgende metode, der indeholder en prøve
blok med to udgangspunkter. I dette eksempel er begge udgangspunkter Vend tilbage
udsagn:
statisk int giveMeThatOldFashionedBoolean (boolsk bVal) {prøv {hvis (bVal) {retur 1; } returner 0; } endelig {System.out.println ("Fik gammeldags."); }}
Ovenstående metode kompileres med følgende bytekoder:
// Bytecode-sekvensen for prøveblokken: 0 iload_0 // Push lokal variabel 0 (arg bestået som divisor) 1 ifeq 11 // Push lokal variabel 1 (arg bestået som udbytte) 4 iconst_1 // Push int 1 5 istore_3 // Pop en int (1), gem i lokal variabel 3 6 jsr 24 // Spring til mini-subrutinen for den sidste paragraf 9 iload_3 // Skub lokal variabel 3 (1) 10 ireturn // Returner int oven på stak (1) 11 ikonst_0 // Push int 0 12 istore_3 // Pop et int (0), gem i lokal variabel 3 13 jsr 24 // Spring til mini-subrutinen for den sidste paragraf 16 iload_3 // Push local variabel 3 (0) 17 ireturn // Returner int på toppen af stakken (0) // Bytecode-sekvensen for en fangstklausul, der fanger enhver form for undtagelse // kastet fra prøveblokken. 18 astore_1 // Pop henvisningen til den kastede undtagelse, gem // i lokal variabel 1 19 jsr 24 // Spring til mini-subrutinen for den endelige paragraf 22 aload_1 // Skub henvisningen (til den kastede undtagelse) fra // lokal variabel 1 23 athrow // Gentag den samme undtagelse // Miniaturens underrutine, der implementerer den endelige blok. 24 astore_2 // Pop returadressen, gem den i lokal variabel 2 25 getstatic # 8 // Få en reference til java.lang.System.out 28 ldc # 1 // Skub fra den konstante pool 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Vend tilbage til returadresse gemt i lokal variabel 2
Bykoderne til prøve
blok inkluderer to jsr instruktioner. En anden jsr instruktion er indeholdt i fangst
klausul. Det fangst
klausul tilføjes af kompilatoren, fordi hvis en undtagelse kastes under udførelsen af prøve
blok, den sidste blok skal stadig udføres. Derfor er den fangst
klausul påberåber sig blot den miniaturesubrutine, der repræsenterer langt om længe
klausul, kaster derefter den samme undtagelse igen. Undtagelsestabellen for giveMeThatOldFashionedBoolean ()
Metoden, vist nedenfor, angiver, at enhver undtagelse, der smides mellem og inkluderer adresserne 0 og 17 (alle bytekoder, der implementerer prøve
blok) håndteres af fangst
klausul, der starter på adresse 18.
Undtagelsestabel: fra til måltype 0 18 18 enhver
Bytekoderne til langt om længe
klausul begynder med at poppe returadressen fra stakken og gemme den i lokal variabel to. I slutningen af langt om længe
klausul, den ret instruktion tager sin returadresse fra det rette sted, lokal variabel to.
HopAround: En Java-virtuel maskinsimulering
Appletten nedenfor demonstrerer en Java-virtuel maskine, der udfører en sekvens af bytekoder. Bytecode-sekvensen i simuleringen blev genereret af javac
kompilator til hopAround ()
metode i klassen vist nedenfor:
klasse Clown {statisk int hopAround () {int i = 0; mens (sand) {prøv {prøv {i = 1; } endelig {// den første endelig klausul i = 2; } i = 3; returnere i; // dette fuldender aldrig, på grund af fortsættelsen} endelig {// den anden endelig klausul, hvis (i == 3) {fortsætter; // dette fortsætter tilsidesætter returerklæringen}}}}}
Bykoderne genereret af javac
til hopAround ()
metode er vist nedenfor: