Definisane i demonstrirane klauzule o pokušaju konačno

Dobrodošli u još jedan dio Under the Hood . Ovaj stupac daje programerima Java uvid u tajanstvene mehanizme koji klikću i vrte se ispod pokrenutih Java programa. Ovomjesečni članak nastavlja raspravu o skupu naredbi bajtkoda Java virtualnog stroja (JVM). Njegov je fokus na načinu na koji JVM obrađuje finallyklauzule i bajt kodove koji su relevantni za te klauzule.

Napokon: Nešto za razveseliti

Dok Java virtualni stroj izvršava bytecode-ove koji predstavljaju Java program, može na jedan od nekoliko načina izaći iz bloka koda - izjava između dviju podudarnih kovrčavih zagrada. Kao prvo, JVM je jednostavno mogao izvršiti nakon zatvaranja kovrčavih zagrada bloka koda. Ili bi mogao naići na izjavu prekida, nastavka ili povratka zbog koje će iskočiti iz bloka koda negdje iz sredine bloka. Konačno, mogla bi se izbaciti iznimka koja uzrokuje da JVM skoči na odgovarajuću klauzulu catch ili, ako ne postoji odgovarajuća klauzula catch, prekine nit. S tim potencijalnim izlaznim točkama koje postoje unutar jednog bloka koda, poželjno je na jednostavan način izraziti da se nešto dogodilo bez obzira na to kako se iz bloka koda izašlo. U Javi se takva želja izražava s pomoćutry-finally klauzula.

Da biste koristili try-finallyklauzulu:

  • u tryblok priložiti kôd koji ima više izlaznih točaka i

  • stavite u finallyblok kod koji se mora dogoditi bez obzira na to kako se iz trybloka izlazi.

Na primjer:

try {// Blok koda s više izlaznih točaka} napokon {// Blok koda koji se uvijek izvršava kad se izađe iz bloka try, // bez obzira kako je blok try izašao} 

Ako su catchs tryblokom povezane bilo koje klauzule , finallyklauzulu morate staviti nakon svih catchklauzula, kao u:

pokušajte {// Blok koda s više izlaznih točaka} catch (Cold e) {System.out.println ("Hladno!"); } catch (APopFly e) {System.out.println ("Uhvatio pop muhu!"); } catch (SomeonesEye e) {System.out.println ("Uhvatio nečiji pogled!"); } konačno {// Blok koda koji se uvijek izvršava kad se izađe iz bloka try, // bez obzira na to kako se blok try izlazi. System.out.println ("Je li to nešto zbog čega treba navijati?"); }

Ako se tijekom izvršavanja koda unutar trybloka izbaci iznimka kojom se rukuje catchklauzulom pridruženom trybloku, finallyklauzula će se izvršiti nakon catchklauzule. Na primjer, ako Coldse tijekom izvršavanja naredbi (nije prikazano) u trygornjem bloku izuzme izuzetak , sljedeći će se tekst zapisati u standardni izlaz:

Uhvaćen hladno! Je li to nešto zbog čega treba navijati?

Klauzule try-konačno u bajt kodovima

U bajtkodovima, finallyklauzule djeluju kao minijaturne potprograme unutar metode. Na svakoj izlaznoj točki unutar trybloka i s catchnjim povezanih klauzula naziva se minijaturna potprogram koji odgovara finallyklauzuli. Nakon finallydovršenja klauzule - sve dok se dovršava izvršavanjem prošlosti posljednje izjave u finallyklauzuli, a ne izbacivanjem iznimke ili izvršavanjem povratka, nastavka ili prekida - sama minijaturna potprogram se vraća. Izvršenje se nastavlja neposredno nakon točke kada je minijaturna potprogram uopće pozvana, tako da se iz trybloka može izaći na odgovarajući način.

Opcode koji JVM uspijeva skočiti na minijaturnu potprogram je jsr uputa. JSR poduka traje od dva bajta operand, offset iz položaja JSR nastave gdje počinje minijaturni potprogram. Druga varijanta naredbe jsr je jsr_w , koja izvršava istu funkciju kao i jsr, ali uzima širok (četverobajtni) operand. Kada JVM naiđe na naredbu jsr ili jsr_w , gura povratnu adresu na stog, a zatim nastavlja izvršavanje na početku minijaturne potprograma. Povratna adresa je pomak bytecode-a neposredno nakon jsr-a ilijsr_w uputa i njezini operandi.

Nakon završetka minijaturne potprograma, ona poziva ret naredbu koja se vraća iz potprograma. Mirovini instrukcija traje jedan operand, indeks u lokalnim varijablama u kojem je spremljena povratna adresa. Opkodovi koji se bave finallyklauzulama sažeti su u sljedećoj tablici:

Konačno klauzule
Opcode Operand (i) Opis
jsr grana bajta1, grana bajta2 gura povratnu adresu, grane se pomiču
jsr_w grana bajta1, grana bajta2, grana bajta3, grana bajta4 gura povratnu adresu, grane se široko pomaknu
ret indeks vraća se na adresu pohranjenu u indeksu lokalne varijable

Nemojte brkati minijaturnu potprogram s Java metodom. Java metode koriste drugačiji skup uputa. Upute poput invokevirtual ili invokenonvirtual uzrok Java metoda se zazivaju i upute kao što su povratak , areturn ili ireturn uzrok Java metodu da se vrate. JSR instrukcija ne uzrokuje Java metoda treba pozivati. Umjesto toga, uzrokuje skok na drugi opcode unutar iste metode. Isto tako, naredba ret se ne vraća iz metode; nego se vraća natrag u opcode u istoj metodi koja odmah slijedi pozivajuću jsr naredbu i njene operande. Bajtkodovi koji implementiraju afinallyklauzule nazivaju se minijaturnom potprogramom jer djeluju poput male potprograme unutar toka bytecode-a jedne metode.

Mogli biste pomisliti da bi naredba ret trebala izbaciti povratnu adresu iz steka , jer ju je tu gurnula naredba jsr . Ali nije. Umjesto toga, na početku svake potprograma povratna adresa iskače s vrha stoga i pohranjuje se u lokalnu varijablu - istu onu lokalnu varijablu iz koje je kasnije dobiva naredba ret . To asimetrično način rada s povratnu adresu je potrebno, jer na kraju rečenice (a time, minijaturne potprograme) sami mogu baciti iznimke ili uključiti return, breakili continueizjave. Zbog ove mogućnosti, dodatna povratna adresa koju je jsr gurnuo na stognastava se mora ukloniti iz dimnjaka odmah, tako da neće uvijek biti tu ako se finallyklauzula izlazi s break, continue, return, ili bačena iznimka. Stoga se povratna adresa pohranjuje u lokalnu varijablu na početku finallyminijaturne potprograme bilo koje klauzule.

Kao ilustraciju uzmimo sljedeći kôd koji uključuje finallyklauzulu koja izlazi s naredbom break. Rezultat ovog koda je da, bez obzira na parametar bVal proslijeđen metodi surpriseTheProgrammer(), metoda vraća false:

statičko logičko iznenađenjeTheProgrammer (logički bVal) {while (bVal) {try {return true; } napokon {break; }} return false; }

Gornji primjer pokazuje zašto se povratna adresa mora pohraniti u lokalnu varijablu na početku finallyklauzule. Budući da finallyklauzula izlazi s prekidom, ona nikada ne izvršava naredbu ret . Kao rezultat toga, JVM se nikad ne vraća da završi return trueizjavu. Umjesto toga, samo se nastavlja s breaki spušta se kraj završne kovrčave zagrade whileizjave. Sljedeća je izjava " return false,", što je upravo ono što JVM radi.

Ponašanje prikazano finallyklauzulom koja izlazi s a breaktakođer je prikazano finallyklauzulama koje izlaze s returnili continueili izbacivanjem iznimke. Ako finallyklauzula izađe iz bilo kojeg od ovih razloga, naredba ret na kraju finallyklauzule nikada se ne izvršava. Budući da se za naredbu ret ne može izvršiti, na nju se ne može pouzdati da se adresa povratnika ukloni iz stoga. Stoga se povratna adresa pohranjuje u lokalnu varijablu na početku finallyminijaturne potprograme klauzule.

Za cjelovit primjer razmotrite sljedeću metodu koja sadrži tryblok s dvije izlazne točke. U ovom su primjeru obje izlazne točke returnizjave:

static int giveMeThatOldFashionedBoolean (logički bVal) {try {if (bVal) {return 1; } return 0; } napokon {System.out.println ("Staromodno."); }}

Gornja metoda kompilira se u sljedeće bajtkodove:

// Redoslijed byte-koda za blok try: 0 iload_0 // Guranje lokalne varijable 0 (arg prosljeđuje se kao djelitelj) 1 ifeq 11 // Guranje lokalne varijable 1 (arg prosljeđuje kao dividenda) 4 iconst_1 // Push int 1 5 istore_3 // Otvorite int (the 1), spremite u lokalnu varijablu 3 6 jsr 24 // Skočite na mini potprogram za konačno klauzulu 9 iload_3 // Gurnite lokalnu varijablu 3 (the 1) 10 ireturn // Vratite int na vrh stack (the 1) 11 iconst_0 // Push int 0 12 istore_3 // Pop an int (the 0), spremi u lokalnu varijablu 3 13 jsr 24 // Skok na mini potprogram za konačno klauzulu 16 iload_3 // Push local varijabla 3 (0) 17 ireturn // Povratak int na vrh stoga (0) // Slijed bajt koda za klauzulu catch koja hvata bilo kakvu iznimku // izbačenu iz bloka try. 18 astore_1 // Pokažite referencu na bačenu iznimku,pohraniti // u lokalnu varijablu 1 19 jsr 24 // Skok na mini-potprogram za konačno klauzulu 22 aload_1 // Pritisnite referencu (na izbačenu iznimku) iz // lokalne varijable 1 23 athrow // Ponovno unesite istu iznimku / / Minijaturna potprogram koji provodi konačno blok. 24 astore_2 // Otvorite povratnu adresu, pohranite je u lokalnu varijablu 2 25 getstatic # 8 // Dohvatite referencu na java.lang.System.out 28 ldc # 1 // Guranje iz konstantnog spremišta 30 invokevirtual # 7 // Poziv System.out.println () 33 ret 2 // Povratak na povratnu adresu pohranjenu u lokalnoj varijabli 2pohranite ga u lokalnu varijablu 2 25 getstatic # 8 // Nabavite referencu na java.lang.System.out 28 ldc # 1 // Gurnite iz konstantnog spremišta 30 invokevirtual # 7 // Pozovi System.out.println () 33 ret 2 // Povratak na povratnu adresu pohranjenu u lokalnoj varijabli 2pohranite ga u lokalnu varijablu 2 25 getstatic # 8 // Nabavite referencu na java.lang.System.out 28 ldc # 1 // Gurnite iz konstantnog spremišta 30 invokevirtual # 7 // Pozovi System.out.println () 33 ret 2 // Povratak na povratnu adresu pohranjenu u lokalnoj varijabli 2

U bytecodes za tryblok uključuju dvije JSR upute. Druga je jsr uputa sadržana u catchklauzuli. catchKlauzula dodaje prevodilac, jer ako se iznimka bačena za vrijeme izvršavanja trybloka, na kraju bloka dalje mora biti izvršen. Stoga se catchklauzula samo poziva na minijaturnu potprogram koji predstavlja finallyklauzulu, a zatim ponovno izbacuje istu iznimku. Tablica izuzetaka za giveMeThatOldFashionedBoolean()metodu, prikazana u nastavku, ukazuje na to da se bilo kojom iznimkom između i uključujući adrese 0 i 17 (svi bajt kodovi koji implementiraju tryblok) obrađuje catchklauzulom koja počinje na adresi 18.

Tablica izuzetaka: od do ciljane vrste 0 18 18 bilo koja 

Bajtkodovi finallyklauzule započinju iskakanjem povratne adrese iz steka i pohranjivanjem u lokalnu varijablu dva. Na kraju finallyklauzule, naredba ret uzima svoju povratnu adresu s odgovarajućeg mjesta, lokalne varijable dva.

HopAround: simulacija Java virtualnog stroja

Aplet u nastavku prikazuje Java virtualni stroj koji izvršava slijed bajt kodova. Slijed bajt koda u simulaciji generirao je javacprevodilac za hopAround()metodu klase prikazane u nastavku:

klasa Clown {static int hopAround () {int i = 0; while (true) {try {try {i = 1; } napokon {// prva napokon klauzula i = 2; } i = 3; return i; // ovo se nikada ne dovršava, zbog continue} napokon {// druga konačno klauzula if (i == 3) {continue; // ovo nastavak poništava povratnu izjavu}}}}}

Bajtkodovi generirani javacza hopAround()metodu prikazani su u nastavku: