Vítejte v další splátce Under The Hood. Tento sloupec poskytuje vývojářům v Javě pohled na záhadné mechanismy klikání a vrčení pod jejich spuštěnými programy Java. Článek z tohoto měsíce pokračuje v diskusi o instrukční sadě bajtových kódů virtuálního stroje Java (JVM). Zaměřuje se na způsob, jakým JVM zachází Konečně
klauzule a bytové kódy, které jsou relevantní k těmto klauzulím.
Nakonec: Něco na rozveselení
Protože virtuální stroj Java provádí bajtové kódy, které představují program Java, může jedním z několika způsobů ukončit blok kódu - příkazy mezi dvěma shodnými složenými závorkami. Za prvé, JVM jednoduše mohl provést za závěrečnou složenou závorkou bloku kódu. Nebo se může setkat s příkazem break, continue nebo return, který způsobí, že vyskočí z bloku kódu odkudkoli uprostřed bloku. Nakonec může být vyvolána výjimka, která způsobí, že JVM buď skočí na odpovídající klauzuli catch, nebo, pokud neexistuje odpovídající klauzule catch, vlákno ukončit. S těmito potenciálními výstupními body existujícími v jednom bloku kódu je žádoucí mít snadný způsob, jak vyjádřit, že se něco stalo bez ohledu na to, jak je blok kódu opuštěn. V Javě je taková touha vyjádřena pomocí zkuste-konečně
doložka.
Chcete-li použít a zkuste-konečně
doložka:
uzavřít do a
Snaž se
- zablokovat kód, který má více výstupních bodů, a -vložte do
Konečně
zablokovat kód, který se musí stát bez ohledu na to, jakSnaž se
blok je ukončen.
Například:
zkuste {// Blok kódu s více výstupními body} konečně {// Blok kódu, který se vždy provede při ukončení bloku try, // bez ohledu na to, jak se ukončí blok try}
Pokud nějaké máte chytit
doložky spojené s Snaž se
blok, musíte dát Konečně
klauzule po všem chytit
klauzule, jako v:
try {// Blok kódu s více výstupními body} catch (Cold e) {System.out.println ("Caught cold!"); } catch (APopFly e) {System.out.println ("Caught a pop fly!"); } catch (SomeonesEye e) {System.out.println ("Přitáhlo něčí oko!"); } nakonec {// Blok kódu, který se provede vždy, když je ukončen blok try, // bez ohledu na to, jak je blok try ukončen. System.out.println ("Je to něco na rozveselení?"); }
Pokud během provádění kódu v rámci a Snaž se
blok, je vyvolána výjimka, která je zpracována a chytit
doložka spojená s Snaž se
blok, Konečně
klauzule bude provedena po chytit
doložka. Například pokud a Studený
výjimka je vyvolána během provádění příkazů (není zobrazeno) v Snaž se
o blok výše by byl na standardní výstup zapsán následující text:
Nachlazení! Je to něco na rozveselení?
Klauzule Try-finally v bajtových kódech
V bytecodes, Konečně
klauzule fungují jako miniaturní podprogramy v rámci metody. V každém výstupním bodě uvnitř a Snaž se
blok a související chytit
klauzule, miniaturní podprogram, který odpovídá Konečně
klauzule se nazývá. Po Konečně
klauzule je dokončena - pokud je dokončena provedením za posledním příkazem v souboru Konečně
klauzule, nikoli vyvoláním výjimky nebo provedením návratu, pokračování nebo přerušení - vrací se samotný miniaturní podprogram. Provádění pokračuje těsně za bodem, kde byl miniaturní podprogram volán na prvním místě, tedy Snaž se
blok lze příslušným způsobem opustit.
Operační kód, který způsobí, že JVM přeskočí na miniaturní podprogram, je jsr návod. The jsr instrukce bere dvoubajtový operand, posun od umístění jsr instrukce, kde začíná miniaturní podprogram. Druhá varianta jsr instrukce je jsr_w, který plní stejnou funkci jako jsr ale trvá široký (čtyřbajtový) operand. Když JVM narazí na a jsr nebo jsr_w instrukce, vloží zpáteční adresu do zásobníku a poté pokračuje v provádění na začátku miniaturního podprogramu. Zpáteční adresa je posunutí bytového kódu bezprostředně za jsr nebo jsr_w instrukce a její operandy.
Po dokončení miniaturního podprogramu vyvolá ret instrukce, která se vrací z podprogramu. The ret instrukce bere jeden operand, index do lokálních proměnných, kde je uložena zpáteční adresa. Opcodes, které se zabývají Konečně
klauzule jsou shrnuty v následující tabulce:
Operační kód | Operand (s) | Popis |
---|---|---|
jsr | branchbyte1, branchbyte2 | posune zpáteční adresu, větve k odsazení |
jsr_w | branchbyte1, branchbyte2, branchbyte3, branchbyte4 | posune zpáteční adresu, větve na široký offset |
ret | index | vrací na adresu uloženou v indexu lokálních proměnných |
Nezaměňujte miniaturní podprogram s metodou Java. Metody Java používají jinou sadu pokynů. Pokyny jako invokevirtual nebo invokenonvirtual způsobí vyvolání metody Java a pokynů, jako je vrátit se, návratnebo zpět způsobí návrat metody Java. The jsr instrukce nezpůsobí vyvolání metody Java. Místo toho způsobí skok na jiný operační kód v rámci stejné metody. Stejně tak ret instrukce se nevrátí z metody; spíše se vrátí zpět na operační kód ve stejné metodě, která bezprostředně následuje po volání jsr instrukce a její operandy. Bajtové kódy, které implementují a Konečně
Klauzule se nazývají miniaturní podprogram, protože fungují jako malý podprogram v proudu bytecode jedné metody.
Možná si myslíte, že ret instrukce by měla vyskakovat zpáteční adresu ze zásobníku, protože tam ji tlačil jsr návod. Ale není. Místo toho se na začátku každého podprogramu zpáteční adresa vyskočí z horní části zásobníku a uloží se do místní proměnné - stejné místní proměnné, ze které ret instrukce později dostane. Tento asymetrický způsob práce se zpáteční adresou je nezbytný, protože konečně klauzule (a tedy miniaturní podprogramy) samy o sobě mohou vyvolat výjimky nebo zahrnout vrátit se
, přestávka
nebo pokračovat
prohlášení. Z důvodu této možnosti byla extra zpáteční adresa, která byla do zásobníku vložena jsr instrukce musí být okamžitě odstraněna ze zásobníku, takže tam stále nebude, pokud Konečně
klauzule končí s a přestávka
, pokračovat
, vrátit se
, nebo vyvolána výjimka. Proto je zpáteční adresa uložena do místní proměnné na začátku kteréhokoli z nich Konečně
doložka je miniaturní podprogram.
Pro ilustraci zvažte následující kód, který obsahuje a Konečně
klauzule, která končí příkazem break. Výsledkem tohoto kódu je, že bez ohledu na parametr bVal předaný metodě překvapeníTheProgrammer ()
, metoda se vrací Nepravdivé
:
static boolean překvapeníTheProgrammer (boolean bVal) {while (bVal) {try {return true; } konečně {zlomit; }} vrátit false; }
Výše uvedený příklad ukazuje, proč musí být zpáteční adresa uložena do místní proměnné na začátku Konečně
doložka. Protože Konečně
klauzule končí s přestávkou, nikdy neprovede ret návod. Ve výsledku se JVM nikdy nevrátí a nedokončí „návrat true
"prohlášení. Místo toho jde jen o přestávka
a padá dolů za závěrečnou složenou závorku zatímco
prohlášení. Další prohlášení je „návrat false
„, což je přesně to, co JVM dělá.
Chování zobrazené a Konečně
klauzule, která končí s a přestávka
je také zobrazeno Konečně
klauzule, které opouštějí a vrátit se
nebo pokračovat
nebo vyvoláním výjimky. Pokud Konečně
doložka končí z některého z těchto důvodů, ret instrukce na konci Konečně
klauzule se nikdy neprovede. Protože ret není zaručeno provedení instrukce, nelze se na ni spoléhat při odstranění zpáteční adresy ze zásobníku. Zpáteční adresa je proto uložena do místní proměnné na začátku Konečně
miniaturní podprogram doložky.
Úplný příklad zvažte následující metodu, která obsahuje a Snaž se
blok se dvěma výstupními body. V tomto příkladu jsou oba výstupní body vrátit se
prohlášení:
static int giveMeThatOldFashionedBoolean (boolean bVal) {try {if (bVal) {return 1; } návrat 0; } konečně {System.out.println ("Staromódní."); }}
Výše uvedená metoda se kompiluje do následujících bajtových kódů:
// Sekvence bytecode pro blok try: 0 iload_0 // Push lokální proměnná 0 (arg předán jako dělitel) 1 ifeq 11 // Push lokální proměnná 1 (arg předán jako dividenda) 4 iconst_1 // Push int 1 5 istore_3 // Pop an int (the 1), store into local variable 3 6 jsr 24 // Skok na mini-podprogram pro definitivní klauzuli 9 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-subroutine for the finally clause 16 iload_3 // Push local proměnná 3 (0) 17 ireturn // Vrátit int v horní části zásobníku (0) // Sekvence bytecode pro klauzuli catch, která zachytí jakýkoli druh výjimky // vyvolána v rámci bloku try. 18 astore_1 // Popujte odkaz na vyvolanou výjimku, uložte // do lokální proměnné 1 19 jsr 24 // Přeskočte na mini-podprogram pro klauzuli nakonec 22 aload_1 // Posuňte odkaz (na vyvolanou výjimku) z // lokální proměnná 1 23 athrow // Obnoví stejnou výjimku // Miniaturní podprogram, který implementuje konečný blok. 24 astore_2 // Pop zpáteční adresu, uložte ji do místní proměnné 2 25 getstatic # 8 // Získejte odkaz na java.lang.System.out 28 ldc # 1 // Push z konstantního fondu 30 invokevirtual # 7 // Invoke System.out.println () 33 ret 2 // Návrat na zpáteční adresu uloženou v místní proměnné 2
Bajtové kódy pro Snaž se
blok obsahuje dva jsr instrukce. Další jsr instrukce je obsažena v chytit
doložka. The chytit
klauzule je přidána kompilátorem, protože pokud je vyvolána výjimka během provádění Snaž se
blok, konečný blok musí být stále proveden. Proto chytit
klauzule pouze vyvolá miniaturní podprogram, který představuje Konečně
klauzule, pak vyvolá stejnou výjimku znovu. Tabulka výjimek pro giveMeThatOldFashionedBoolean ()
Metoda zobrazená níže označuje, že jakákoli výjimka vyvolaná mezi adresami 0 a 17 včetně (všechny bajtové kódy, které implementují Snaž se
blok) zpracovává chytit
klauzule, která začíná na adrese 18.
Tabulka výjimek: od do cílového typu 0 18 18 libovolná
Bajtové kódy Konečně
klauzule začíná vyskakováním zpáteční adresy ze zásobníku a jejím uložením do lokální proměnné dva. Na konci Konečně
doložka, ret instrukce vezme zpáteční adresu ze správného místa, lokální proměnná dva.
HopAround: Simulace virtuálního stroje Java
Níže uvedený applet ukazuje virtuální stroj Java provádějící sekvenci bajtových kódů. Sekvence bytecode v simulaci byla vygenerována javac
překladač pro hopAround ()
metoda třídy uvedené níže:
třída Klaun {static int hopAround () {int i = 0; while (true) {try {try {i = 1; } nakonec {// první klauzule konečně i = 2; } i = 3; návrat i; // toto se nikdy nedokončí kvůli pokračování} nakonec {// druhá klauzule konečně if (i == 3) {continue; // toto pokračování potlačí návratové prohlášení}}}}}
Bajtové kódy generované javac
pro hopAround ()
níže jsou uvedeny metody: