Velkommen til en annen del av Under panseret. Denne kolonnen gir Java-utviklere et glimt av de mystiske mekanismene som klikker og snurrer under deres kjørende Java-programmer. Denne månedens artikkel fortsetter diskusjonen av bytecode instruksjonssettet til Java virtual machine (JVM). Fokuset er måten JVM håndterer endelig
klausuler og bytekoder som er relevante for disse klausulene.
Til slutt: Noe å heie på
Når den virtuelle Java-maskinen utfører bytekodene som representerer et Java-program, kan den avslutte en kodeblokk - utsagnene mellom to matchende krøllete bukseseler - på en av flere måter. For det første kunne JVM bare utføre forbi den lukkende krøllbøylen til kodeblokken. Eller det kan møte en pause, fortsette eller returnere uttalelse som får den til å hoppe ut av kodeblokken fra et sted midt i blokken. Til slutt kan et unntak kastes som får JVM til å hoppe til en matchende fangstklausul, eller, hvis det ikke er en matchende fangstklausul, å avslutte tråden. Med disse potensielle utgangspunktene som finnes i en enkelt kodeblokk, er det ønskelig å ha en enkel måte å uttrykke at noe skjedde uansett hvordan en blokk med kode avsluttes. I Java uttrykkes et slikt ønske med a prøv-endelig
klausul.
Å bruke en prøv-endelig
klausul:
legg inn i en
prøve
blokker koden som har flere utgangspunkter, ogsette i en
endelig
blokker koden som må skje uansett hvordanprøve
blokk er avsluttet.
For eksempel:
prøv {// Kodeblokk med flere utgangspunkter} til slutt {// Kodeblokk som alltid utføres når prøveblokken avsluttes, // uansett hvordan prøveblokken avsluttes}
Hvis du har noen å fange
klausuler knyttet til prøve
blokker, må du sette endelig
klausul etter alle å fange
klausuler, som i:
prøv {// Kodeblokk med flere utgangspunkter} catch (Cold e) {System.out.println ("Fanget kaldt!"); } catch (APopFly e) {System.out.println ("Fanget en popfly!"); } catch (SomeonesEye e) {System.out.println ("Fanget noens øye!"); } til slutt {// Kodeblokk som alltid utføres når prøveblokken avsluttes, // uansett hvordan prøveblokken avsluttes. System.out.println ("Er det noe å heie på?"); }
Hvis under utførelse av koden i en prøve
blokk, kastes et unntak som håndteres av en å fange
klausul knyttet til prøve
blokk, den endelig
klausul vil bli utført etter å fange
klausul. For eksempel hvis en Kald
unntak kastes under gjennomføring av uttalelsene (ikke vist) i prøve
blokken ovenfor, vil følgende tekst bli skrevet til standardutgangen:
Fikk kaldt! Er det noe å heie på?
Prøv endelig klausuler i bytekoder
I bytekoder, endelig
klausuler fungerer som miniatyrunderrutiner innenfor en metode. Ved hvert utgangssted inne i prøve
blokkering og tilhørende å fange
klausuler, miniatyrundervisningen som tilsvarer endelig
klausul kalles. Etter endelig
klausul fullfører - så lenge den fullføres ved å kjøre forbi den siste setningen i endelig
klausul, ikke ved å kaste et unntak eller utføre en retur, fortsette eller bryte - selve miniatyrundervisningen kommer tilbake. Utførelsen fortsetter like forbi punktet der miniatyrundervisningen ble kalt i utgangspunktet, så prøve
blokkering kan avsluttes på riktig måte.
Opkoden som får JVM til å hoppe til en miniatyrundervisning er jsr instruksjon. De jsr instruksjon tar en to-byte operand, forskyvet fra plasseringen av jsr instruksjon der miniatyrsubrutinen begynner. En annen variant av jsr instruksjon er jsr_w, som utfører samme funksjon som jsr men tar en bred (fire-byte) operand. Når JVM møter en jsr eller jsr_w instruksjon, den skyver en returadresse på bunken, og fortsetter deretter utførelsen ved starten av miniatyrundrutinen. Returadressen er forskyvning av bytekoden umiddelbart etter jsr eller jsr_w instruksjon og dens operander.
Etter at en miniatyrundervisning er fullført, påkaller den ret instruksjon, som kommer tilbake fra underrutinen. De ret instruksjon tar en operand, en indeks i de lokale variablene der returadressen er lagret. Opkodene som håndterer endelig
klausuler er oppsummert i følgende tabell:
Opcode | Operand (er) | Beskrivelse |
---|---|---|
jsr | branchbyte1, branchbyte2 | skyver returadressen, forgrener seg for å kompensere |
jsr_w | branchbyte1, branchbyte2, branchbyte3, branchbyte4 | skyver returadressen, forgrener seg til bred forskyvning |
ret | indeks | går tilbake til adressen som er lagret i lokal variabelindeks |
Ikke forveksle en miniatyrundervisning med en Java-metode. Java-metoder bruker et annet sett med instruksjoner. Instruksjoner som invokevirtual eller invokenonvirtual føre til at en Java-metode påberopes, og instruksjoner som komme tilbake, en retur, eller jeg kommer tilbake føre til at en Java-metode kommer tilbake. De jsr instruksjon fører ikke til at en Java-metode blir påkalt. I stedet forårsaker det et hopp til en annen opcode innen samme metode. Likeledes, den ret instruksjon kommer ikke tilbake fra en metode; snarere går den tilbake til opkoden på samme måte som umiddelbart følger anropet jsr instruksjon og dens operander. Bytekodene som implementerer en endelig
klausul kalles en miniatyrsubrutine fordi de fungerer som en liten subrutine i bytekodestrømmen til en enkelt metode.
Du tror kanskje at ret instruksjonen skal skyve returadressen fra bunken, fordi det er der den ble presset av jsr instruksjon. Men det gjør det ikke. I stedet, ved starten av hver underrutine, blir returadressen poppet av toppen av stabelen og lagret i en lokal variabel - den samme lokale variabelen som ret instruksjon får det senere. Denne asymmetriske måten å jobbe med returadressen på er nødvendig fordi endelig klausuler (og derfor miniatyrunderviser) selv kan kaste unntak eller inkludere komme tilbake
, gå i stykker
, eller Fortsette
uttalelser. På grunn av denne muligheten ble den ekstra returadressen som ble skjøvet på bunken av jsr instruksjonen må fjernes fra bunken med en gang, så den vil fortsatt ikke være der hvis endelig
klausul går ut med en gå i stykker
, Fortsette
, komme tilbake
, eller kastet unntak. Derfor lagres returadressen i en lokal variabel ved starten av hvilken som helst endelig
klausulens miniatyrundervisning.
Ta en titt på følgende kode, som inkluderer en endelig
klausul som går ut med et brudduttalelse. Resultatet av denne koden er at, uavhengig av parameteren bVal overført til metoden surpriseTheProgrammer ()
, returnerer metoden falsk
:
statisk boolsk overraskelseTheProgrammer (boolsk bVal) {mens (bVal) {prøv {return true; } til slutt {pause; }} returner falsk; }
Eksemplet ovenfor viser hvorfor returadressen må lagres i en lokal variabel i begynnelsen av endelig
klausul. Fordi det endelig
klausul går ut med en pause, utfører den aldri ret instruksjon. Som et resultat går JVM aldri tilbake for å fullføre "returner sant
"uttalelse. I stedet fortsetter det bare med gå i stykker
og faller ned forbi den lukkende krøllbøylen til samtidig som
uttalelse. Neste uttalelse er "returner falsk
, "som er nettopp det JVM gjør.
Oppførselen vist av a endelig
klausul som går ut med en gå i stykker
er også vist av endelig
klausuler som går ut med en komme tilbake
eller Fortsette
, eller ved å kaste et unntak. Hvis en endelig
klausul utgår av noen av disse grunnene, ret instruksjon på slutten av endelig
klausul blir aldri utført. Fordi det ret instruksjon er ikke garantert å bli utført, det kan ikke stole på å fjerne returadressen fra bunken. Derfor lagres returadressen i en lokal variabel i begynnelsen av endelig
klausulens miniatyrundervisning.
For et komplett eksempel, vurder følgende metode, som inneholder en prøve
blokk med to utgangspunkter. I dette eksemplet er begge utgangspunkter komme tilbake
uttalelser:
statisk int giveMeThatOldFashionedBoolean (boolsk bVal) {prøv {hvis (bVal) {retur 1; } returner 0; } til slutt {System.out.println ("Fikk gammeldags."); }}
Ovennevnte metode samler seg til følgende bytekoder:
// Bytekodesekvensen for prøveblokken: 0 iload_0 // Push local variable 0 (arg passed as divisor) 1 ifeq 11 // Push local variable 1 (arg passed as dividend) 4 iconst_1 // Push int 1 5 istore_3 // Popp en int (1), lagre i lokal variabel 3 6 jsr 24 // Hopp til mini-subrutinen for den endelige paragrafen 9 iload_3 // Trykk lokal variabel 3 (1) 10 ireturn // Return int på toppen av stack (the) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), lagre i lokal variabel 3 13 jsr 24 // Jump to the mini-subrutine for the final paragraf 16 iload_3 // Push local variabel 3 (0) 17 ireturn // Returner int på toppen av stakken (0) // Bytkodesekvensen for en fangstklausul som fanger ethvert unntak // kastet fra prøveblokken. 18 astore_1 // Pop henvisningen til kastet unntak, lagre // i lokal variabel 1 19 jsr 24 // Hopp til mini-underrutinen for den endelige paragrafen 22 aload_1 // Skyv referansen (til kastet unntak) fra // lokal variabel 1 23 athrow // Rethrow the same exception // Miniatyrundrutinen som implementerer den endelige blokken. 24 astore_2 // Pop returadressen, lagre den i lokal variabel 2 25 getstatic # 8 // Få en referanse til java.lang.System.out 28 ldc # 1 // Push from the constant pool 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Gå tilbake til returadressen som er lagret i lokal variabel 2
Bytekodene for prøve
blokker inkluderer to jsr bruksanvisning. En annen jsr instruksjonene er inneholdt i å fange
klausul. De å fange
klausul er lagt til av kompilatoren fordi hvis et unntak blir kastet under utførelsen av prøve
blokk, den endelige blokken må fortsatt utføres. derfor å fange
klausul påberoper seg bare miniatyrundervisningen som representerer endelig
klausul, kaster deretter det samme unntaket igjen. Unntakstabellen for giveMeThatOldFashionedBoolean ()
Metoden, vist nedenfor, indikerer at unntak som kastes mellom og inkluderer adressene 0 og 17 (alle bytekodene som implementerer prøve
blokk) håndteres av å fange
klausul som starter på adresse 18.
Unntakstabell: fra til måltype 0 18 18 any
Bytekodene til endelig
klausul begynner med å poppe returadressen fra bunken og lagre den i lokal variabel to. På slutten av endelig
klausul, den ret instruksjonen tar returadressen fra riktig sted, lokal variabel to.
HopAround: En Java virtuell maskinsimulering
Appleten nedenfor viser en Java-virtuell maskin som utfører en sekvens av bykoder. Bytekodesekvensen i simuleringen ble generert av javac
kompilator for hopAround ()
metoden i klassen vist nedenfor:
klasse Clown {statisk int hopAround () {int i = 0; while (true) {prøv {prøv {i = 1; } til slutt {// den første endelig ledd i = 2; } i = 3; returnere jeg; // dette fullføres aldri, på grunn av fortsett} til slutt {// den andre endelig klausulen hvis (i == 3) {fortsetter; // dette fortsetter tilsidesetter returoppgaven}}}}}
Bytekodene generert av javac
for hopAround ()
metoden er vist nedenfor: