Vitajte v ďalšej splátke produktu Pod kapotou. Tento stĺpec poskytuje vývojárom Java prehľad tajomných mechanizmov klikajúcich a vrčiacich pod ich spustenými programami Java. Tento mesiac článok pokračuje v diskusii o inštrukčnej sade bytových kódov virtuálneho stroja Java (JVM). Zameriava sa na spôsob, akým JVM narába konečne
doložky a bajtkódy, ktoré sú relevantné k týmto doložkám.
Na záver: Niečo na povzbudenie
Keď virtuálny stroj Java vykonáva bajtové kódy, ktoré predstavujú program Java, môže jedným z niekoľkých spôsobov opustiť blok kódu - príkazy medzi dvoma zodpovedajúcimi zloženými zátvorkami. Za prvé, JVM jednoducho mohol vykonať za zatváracou zloženou zátvorkou bloku kódu. Alebo by sa mohol stretnúť s príkazom prerušenia, pokračovania alebo vrátenia, ktorý spôsobí, že vyskočí z bloku kódu odkiaľkoľvek uprostred bloku. Nakoniec by mohla byť vyvolaná výnimka, ktorá spôsobí, že JVM buď skočí na zodpovedajúcu klauzulu catch, alebo, ak neexistuje zodpovedajúca klauzula catch, vlákno ukončí. Pretože tieto potenciálne výstupné body existujú v jednom bloku kódu, je žiaduce mať jednoduchý spôsob, ako vyjadriť, že sa niečo stalo bez ohľadu na to, ako je blok kódu opustený. V Jave je takáto túžba vyjadrená a skús-konečne
doložka.
Ak chcete použiť a skús-konečne
doložka:
uzavrieť do a
skús
- zablokovať kód s viacerými výstupnými bodmi a -dať do a
konečne
Blokovať kód, ktorý sa musí stať bez ohľadu na to, akoskús
blok je opustený.
Napríklad:
skúste {// Blok kódu s viacerými výstupnými bodmi} konečne {// Blok kódu, ktorý sa vykoná vždy, keď je blok try ukončený, // bez ohľadu na to, ako je blok try ukončený}
Ak nejaké máte chytiť
doložky spojené s skús
blok, musíte dať konečne
doložka po všetkých chytiť
doložky, ako v:
try {// Blok kódu s viacerými výstupnými bodmi} catch (Cold e) {System.out.println ("Caught cold!"); } chytit (APopFly e) {System.out.println ("Caught a pop fly!"); } catch (SomeonesEye e) {System.out.println ("Prichytil niekoho oko!"); } konečne {// Blok kódu, ktorý sa vykoná vždy pri ukončení bloku try, // bez ohľadu na to, ako je blok try ukončený. System.out.println ("Je to niečo, na čo by ste mali fandiť?"); }
Ak počas vykonávania kódu v rámci a skús
blok, vyvolá sa výnimka, ktorú spracuje a chytiť
doložka spojená s skús
blok, konečne
doložka sa vykoná po chytiť
doložka. Napríklad ak a Chladný
výnimka je vyvolaná počas vykonávania príkazov (nezobrazené) v skús
blok vyššie, na štandardný výstup by sa zapísal nasledujúci text:
Prechladnutý! Je to niečo na povzbudenie?
Klauzuly try-konečne v bytových kódoch
V bytecodes, konečne
vety pôsobia ako miniatúrne podprogramy v rámci metódy. Na každom výstupnom bode vo vnútri a skús
blok a s ním spojené chytiť
klauzuly, miniatúrny podprogram, ktorý zodpovedá konečne
doložka sa volá. Po konečne
klauzula sa dokončuje - pokiaľ sa dokončuje vykonaním za posledným príkazom v kóde konečne
klauzula, nie zrušením výnimky alebo vykonaním návratu, pokračovať alebo zlomiť - vráti sa samotný miniatúrny podprogram. Poprava pokračuje tesne za bodom, kde bol na prvom mieste zavolaný miniatúrny podprogram, teda skús
blok je možné vhodným spôsobom opustiť.
Operačný kód, ktorý spôsobí, že JVM preskočí na miniatúrny podprogram, je spol poučenie. The spol inštrukcia berie dvojbajtový operand, odsadenie od polohy spol inštrukcia, kde sa miniatúrny podprogram začína. Druhý variant spol inštrukcia je jsr_w, ktorá vykonáva rovnakú funkciu ako spol ale berie široký (štvorbajtový) operand. Keď JVM narazí na a spol alebo jsr_w inštrukcia, natlačí spiatočnú adresu do zásobníka, potom pokračuje v vykonávaní na začiatku miniatúrneho podprogramu. Spiatočná adresa je posunutím bajtkódu bezprostredne za spol alebo jsr_w inštrukcia a jej operandy.
Po dokončení miniatúrneho podprogramu vyvolá ret inštrukcia, ktorá sa vráti z podprogramu. The ret inštrukcia vezme jeden operand, index do lokálnych premenných, kde je uložená spiatočná adresa. Protokoly, ktoré sa zaoberajú konečne
doložky sú zhrnuté v nasledujúcej tabuľke:
Operačný kód | Operand (y) | Popis |
---|---|---|
spol | branchbyte1, branchbyte2 | posunie spiatočnú adresu, vetvy na odsadenie |
jsr_w | branchbyte1, branchbyte2, branchbyte3, branchbyte4 | posunie spiatočnú adresu, vetvy na široký offset |
ret | index | vráti na adresu uloženú v indexe lokálnych premenných |
Nezamieňajte miniatúrny podprogram s metódou Java. Metódy Java používajú inú sadu pokynov. Pokyny ako napr invokevirtual alebo invokenonvirtual spôsobí vyvolanie metódy Java a inštrukcie ako napr návrat, návratalebo naspäť spôsobiť návrat metódy Java. The spol inštrukcia nespôsobuje vyvolanie metódy Java. Namiesto toho spôsobí v rámci rovnakej metódy skok na iný operačný kód. Rovnako tak ret inštrukcia sa nevráti z metódy; skôr sa vráti späť k operačnému kódu rovnakou metódou, ktorá bezprostredne nasleduje po volaní spol inštrukcia a jej operandy. Bajtové kódy, ktoré implementujú a konečne
Klauzula sa nazýva miniatúrny podprogram, pretože pôsobia ako malý podprogram v prúde bytecode jednej metódy.
Možno si myslíte, že ret inštrukcia by mala vyskladniť spiatočnú adresu zo zásobníka, pretože práve tam ju vytlačil spol poučenie. Ale nie je. Namiesto toho sa na začiatku každého podprogramu návratová adresa vysunie z hornej časti zásobníka a uloží sa do miestnej premennej - tej istej miestnej premennej, z ktorej ret inštrukcia to dostane neskôr. Tento asymetrický spôsob práce so spiatočnou adresou je nevyhnutný, pretože nakoniec samotné klauzuly (a teda miniatúrne podprogramy) môžu spôsobiť výnimky alebo zahrnúť návrat
, prestávka
alebo ďalej
Vyhlásenia. Z dôvodu tejto možnosti bola dodatočná návratová adresa, ktorá bola do zásobníka vložená serverom spol inštrukcia musí byť ihneď odstránená zo stohu, takže tam stále nebude, ak konečne
doložka končí s a prestávka
, ďalej
, návrat
, alebo hodená výnimka. Preto je spiatočná adresa uložená do lokálnej premennej na začiatku ktorejkoľvek z nich konečne
miniatúrny podprogram doložky.
Pre ilustráciu zvážte nasledujúci kód, ktorý obsahuje a konečne
klauzula, ktorá končí príkazom break. Výsledkom tohto kódu je, že bez ohľadu na parameter bVal odovzdaný metóde prekvapenieTheProgrammer ()
, metóda sa vráti nepravdivé
:
static boolean prekvapenieTheProgrammer (boolean bVal) {while (bVal) {try {return true; } konečne {prestávka; }} return false; }
Vyššie uvedený príklad ukazuje, prečo musí byť spiatočná adresa uložená do lokálnej premennej na začiatku konečne
doložka. Pretože konečne
klauzula končí s prerušením, nikdy sa nevykoná ret poučenie. Výsledkom je, že JVM sa nikdy nevráti a nedokončí „návrat pravdivý
"vyhlásenie. Namiesto toho ide ďalej o." prestávka
a padá dole za zatváraciu kučeravú zátvorku zatiaľ čo
vyhlásenie. Nasledujúce vyhlásenie je „návrat nepravdivý
„, čo je presne to, čo JVM robí.
Správanie zobrazené a konečne
klauzula, ktorá končí s a prestávka
sa tiež zobrazuje ako konečne
doložky, ktoré vystupujú s a návrat
alebo ďalej
, alebo zrušením výnimky. Ak konečne
doložka končí z niektorého z týchto dôvodov, ret inštrukcia na konci konečne
doložka sa nikdy nevykoná. Pretože ret inštrukcia nie je zaručene vykonaná, nemožno sa na ňu spoľahnúť pri odstránení spiatočnej adresy zo zásobníka. Preto je spiatočná adresa uložená do lokálnej premennej na začiatku konečne
miniatúrny podprogram doložky.
Pre úplný príklad zvážte nasledujúcu metódu, ktorá obsahuje a skús
blok s dvoma výstupnými bodmi. V tomto príklade sú oba výstupné body návrat
Vyhlásenia:
static int giveMeThatOldFashionedBoolean (boolean bVal) {try {if (bVal) {return 1; } návrat 0; } konečne {System.out.println ("Staromódne."); }}
Vyššie uvedená metóda sa kompiluje do nasledujúcich bajtových kódov:
// Sekvencia bytecode pre blok try: 0 iload_0 // Zatlač lokálnu premennú 0 (arg odovzdaný ako deliteľ) 1 ifeq 11 // Stlač lokálnu premennú 1 (arg odovzdaný ako dividenda) 4 iconst_1 // Zatlač int 1 5 istore_3 // Vyklopte int (1), uložte do lokálnej premennej 3 6 jsr 24 // Preskočte na mini-podprogram pre klauzulu finally iload_3 // Push local variable 3 (the 1) 10 ireturn // Return int on the top stack (the 1) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), store into local variable 3 13 jsr 24 // Jump to the mini-subrutine for the finally clause 16 iload_3 // Push local premenná 3 (0) 17 ireturn // Return int na vrchu zásobníka (0) // Sekvencia bytecode pre klauzulu catch, ktorá zachytáva akýkoľvek druh výnimky // vyvolaná zvnútra bloku try. 18 astore_1 // Vyklopte odkaz na vyvolanú výnimku, uložte // do lokálnej premennej 1 19 jsr 24 // Prejdite na mini-podprogram pre klauzulu finally 22 aload_1 // Posuňte odkaz (na vyvolanú výnimku) z // lokálna premenná 1 23 athrow // Obnoví rovnakú výnimku // Miniatúrny podprogram, ktorý implementuje blok konečne. 24 astore_2 // Vyplňte spiatočnú adresu, uložte ju do lokálnej premennej 2 25 getstatic # 8 // Získajte odkaz na java.lang.System.out 28 ldc # 1 // Zatlačte z konštantnej oblasti 30 invokevirtual # 7 // Vyvolať System.out.println () 33 ret 2 // Návrat na návratovú adresu uloženú v lokálnej premennej 2
Bajtkódy pre skús
blok zahŕňajú dva spol inštrukcie. Ďalší spol inštrukcia je obsiahnutá v chytiť
doložka. The chytiť
klauzula je pridaná kompilátorom, pretože ak dôjde k výnimke počas vykonávania skús
blok, blok konečne musí byť stále vykonaný. Preto chytiť
doložka iba vyvoláva miniatúrny podprogram, ktorý predstavuje konečne
klauzula, potom sa hodí rovnaká výnimka znova. Tabuľka výnimiek pre giveMeThatOldFashionedBoolean ()
Metóda uvedená nižšie naznačuje, že akákoľvek výnimka vyvolaná medzi adresami 0 a 17 vrátane (všetky bytecodes, ktoré implementujú skús
blok) vybavuje chytiť
doložka, ktorá sa začína na adrese 18.
Tabuľka výnimiek: od do cieľového typu 0 18 18 ľubovoľná
Bajtkódy kódu konečne
doložka začína vyklopením spiatočnej adresy zo zásobníka a jej uložením do lokálnej premennej dva. Na konci konečne
doložka, ret inštrukcia vezme svoju spiatočnú adresu zo správneho miesta, lokálna premenná dva.
HopAround: Simulácia virtuálneho stroja Java
Nižšie uvedený applet demonštruje virtuálny stroj Java vykonávajúci sekvenciu bajtových kódov. Sekvenciu bajtových kódov v simulácii vygeneroval javac
kompilátor pre hopAround ()
metóda triedy uvedená nižšie:
trieda Klaun {static int hopAround () {int i = 0; while (true) {try {try {i = 1; } konečne {// prvá klauzula konečne i = 2; } i = 3; návrat i; // toto sa nikdy nedokončí z dôvodu continue} konečne {// druhá klauzula konečne if (i == 3) {continue; // toto pokračovanie potlačí návratové vyhlásenie}}}}}
Bajtové kódy vygenerované javac
pre hopAround ()
nižšie sú uvedené: