Hlavní navigace

Pohled pod kapotu JVM (12.část - volání metod rozhraní, synchronizované bloky a kontrola typů za běhu)

28. 2. 2012
Doba čtení: 20 minut

Sdílet

Dnes dokončíme popis instrukčního souboru virtuálního stroje Javy. Nejprve budou uvedeny informace o dnes již nepoužívaných instrukcích jsr a ret. Následně se budeme zabývat poměrně vysokoúrovňovými instrukcemi určenými pro volání metod rozhraní, vyhození výjimky, synchronizaci a kontrolu typů za běhu (runtime check).

Obsah

1. Pohled pod kapotu JVM (12.část – volání metod rozhraní, synchronizované bloky a kontrola typů za běhu)

2. Instrukce jsr, jsr_w a ret

3. Problémy s instrukcemi jsr, jsr_wret

4. Instrukce invokeinterface

5. Demonstrační příklad: rozdíl mezi voláním metody rozhraní a běžné virtuální metody

6. Vyhození výjimky s využitím instrukce athrow

7. Synchronizace s využitím instrukcí monitorenter a monitorexit

8. Kontrola typů za běhu: instrukce instanceof a checkcast

9. Odkazy na Internetu

1. Pohled pod kapotu JVM (12.část – volání metod rozhraní, synchronizované bloky a kontrola typů za běhu)

V dnešní části seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje tohoto jazyka si popíšeme všechny zbývající instrukce, které lze najít ve standardním bajtkódu získaného překladem javovských tříd, výčtových typů či rozhraní. Prozatím tedy vynecháme ty instrukce, které se používají v souvislosti s dynamicky typovanými (skriptovacími) jazyky. V předchozích šesti částech tohoto seriálu [1][2][3][4][­5][6] jsme si již většinu instrukcí JVM popsali, ovšem ještě nám zbývá se zmínit o deseti instrukcích sloužících pro různé účely, například pro vyhození výjimky, vstup a výstup ze synchronizovaného bloku, kontrolu typu (objektu) v době běhu programu (runtime check) atd.

Taktéž jsme si prozatím nepopsali instrukce, které se v bajtkódu většinou nevyskytují, a to především z toho důvodu, že jejich použití je z různých důvodů problematické – to je případ instrukcí jsr, jsr_w a ret popsaných ve druhé kapitole, s nimiž se již v bajtkódu vytvářeném moderními implementacemi Javy (JDK6, JDK7) nesetkáme, i když virtuální stroj Javy s těmito instrukcemi umí pracovat.

Začněme však popisem nejjednodušší instrukce virtuálního stroje Javy. Jedná se o instrukci pojmenovanou nop, kterou můžeme najít i v instrukčních sadách reálných mikroprocesorů. Název instrukce nop je vlastně zkratkou vzniklou ze sousloví „no operation“, protože tato instrukce skutečně nic nedělá, pouze v bajtkódu „zabírá“ jeden bajt. V bajtkódu generovaném pomocí překladače javac se s touto instrukcí většinou nesetkáme, ale v případě manipulace s bajtkódem, například s využitím knihovny ASM může být instrukce nop užitečná.

Operační kód instrukce nop je snadno zapamatovatelný, protože se jedná o instrukci ležící na samotném začátku tabulky všech instrukcí, tj. její operační kód je roven 0×00:

# Instrukce Opkód Operandy Prováděná operace
0 nop 0×00 × neprovede žádnou operaci

2. Instrukce jsr, jsr_w a ret

Instrukční soubor virtuálního stroje Javy obsahuje i instrukce nazvané jsr a ret. Instrukce jsr, neboli volání subrutiny, existuje ve dvou variantách. V „krátké“ variantě jsr je za instrukčním kódem uvedena relativní adresa (platná v rámci těla metody) reprezentovaná šestnáctibitovým číslem, zatímco „dlouhá“ varianta jsr_w používá relativní 32bitovou adresu (opět jde o adresu relativní v rámci dané metody). Použití 32bitové adresy je však ve skutečnosti zbytečné, neboť délka metod přeložených do bajtkódu je stejně dalšími okolnostmi omezena na 65535 bajtů. Zajímavé je, že instrukce ret taktéž obsahuje parametr. Jedná se o index lokální proměnné, která obsahuje adresu, na níž se má řízení programu vrátit. Můžeme zde tedy nalézt určitou asymetrii (instrukce jsr totiž ukládá návratovou adresu na zásobník operandů a nikoli do lokální proměnné), která však byla do instrukční sady zavedena naschvál, a to kvůli tomu, jakým způsobem se měly instrukce jsr a ret využívat. Operační kódy obou variant instrukce jsr i jediné varianty instrukce ret jsou vypsány v následující tabulce:

# Instrukce Opkód Operandy Prováděná operace
1 jsr 0×A8 highbyte, lowbyte skok na relativní 16bitovou adresu uloženou v highbyte a lowbyte
2 jsr_w 0×C9 byte1, byte2, byte3, byte4 skok na relativní 32bitovou adresu
3 ret 0×A9 index skok na adresu zapsanou v lokální proměnné

Plný název těchto instrukcí – jump to subroutine a return – sice může navozovat dojem, že se jedná o instrukce použitelné pro volání funkcí či metod (funkce = zjednodušeně řečeno veřejná statická metoda), ve skutečnosti tomu ale tak není, protože instrukce jsr a ret slouží, či lépe řečeno v minulosti sloužily, pro skoky v rámci těla jedné metody. Tyto instrukce byly do JVM přidány z toho důvodu, aby bylo možné opakující se části kódu do bajtkódu zapsat pouze jednou. Ovšem vzhledem k tomu, že překladač prakticky neprovádí žádnou optimalizaci bajtkódu (to by znemožnilo pohodlné ladění), využívaly se tyto instrukce pouze pro zápis těla bloku finally. Důvod je prostý – příkazy zapsané v bloku finally se provedou vždy, tedy nezávisle na tom, zda dojde či nedojde k přerušení běhu metody kvůli vzniklé výjimce, což znamená, že při překladu do bajtkódu se vlastně blok finally (resp. příkazy v něm uvedené) volá z více míst; jak po úspěšném dokončení všech příkazů uvedených v bloku try, tak i po vzniku výjimky a jejím zpracování v bloku catch (těchto bloků samozřejmě může existovat libovolné množství).

3. Problémy s instrukcemi jsr, jsr_wret

Podívejme se na jednoduchý demonstrační příklad, v němž je v metodě pojmenované tryFinally() použit blok try následovaný blokem finally. Touto konstrukcí programátor naznačuje, že se metoda wrapItUp() má zavolat vždy, nezávisle na tom, zda došlo ke vzniku výjimky či nikoli:

class Test1 {
 
    void tryFinally() {
        try {
            tryItOut();
        } finally {
            wrapItUp();
        }
    }
 
    void tryItOut() {}
 
    void wrapItUp() {}
 
}

Starší verze JDK metodu tryFinally() přeložily následujícím způsobem:

Method void tryFinally()
                            // začátek bloku try
   0    aload_0             // uložit this (implicitní parametr) na zásobník operandů
                            // this tedy bude předán metodě tryItOut()
   1    invokevirtual #6    // zavolání metody tryItOut() uvnitř bloku try
   4    jsr 14              // zavolání bloku finally
   7    return              // konec bloku try a současně i konec metody
 
                            // blok zavolaný v případě výskytu výjimky
   8    astore_1            // uložit referenci na objekt reprezentující výjimku
   9    jsr 14              // zavolání bloku finally
  12    aload_1             // načíst referenci na objekt reprezentující výjimku
  13    athrow              // skutečné vyhození výjimky z metody
 
                            // začátek bloku finally
  14    astore_2            // návratová adresa umístěná na TOS instrukcí jsr se uloží do lokální proměnné
  15    aload_0             // uložit this (implicitní parametr) na zásobník operandů
                            // this tedy bude předán metodě wrapItUp()
  16    invokevirtual #5    // zavolání metody wrapItUp() uvnitř bloku finally
  19    ret 2               // návrat z bloku finally (návratová adresa je načtena z proměnné #2)
 
                            // tabulka používaná při práci s výjimkami bude popsána v dalších kapitolách
Exception table:
    From    To  Target      Type
    0       4       8           any

Později se však přišlo na to, že větvení běhu programu s využitím skoku do subrutin zvětšuje – a to mnohdy velmi podstatně – stavový prostor metody, který je kontrolován verifikátorem při načítání bajtkódu. Tento nedostatek mohl být zneužit k DOS útokům, protože virtuálnímu stroji mohl být předán bajtkód se speciálně upravenou metodou obsahující mnoho volání subrutin. Z tohoto důvodu se dnes již s instrukcemi jsr, jsr_w a ret nesetkáme, protože moderní překladače vytvoří následující batjkód:

void tryFinally();
  Code:
                            // začátek bloku try
   0    aload_0             // uložit this (implicitní parametr) na zásobník operandů
                            // this tedy bude předán metodě tryItOut()
   1:   invokevirtual   #2; // zavolání metody tryItOut() uvnitř bloku try
                            // zde začíná první sekvence instrukcí odpovídající bloku finally
   4:   aload_0             // uložit this (implicitní parametr) na zásobník operandů
                            // this tedy bude předán metodě wrapItUp()
   5:   invokevirtual   #3; // zavolání metody wrapItUp()
   8:   goto    18          // konec bloku try
 
   11:  astore_1            // blok zavolaný v případě výskytu výjimky
                            // zde začíná druhá sekvence instrukcí odpovídající bloku finally
   12:  aload_0             // uložit this (implicitní parametr) na zásobník operandů
                            // this tedy bude předán metodě wrapItUp()
   13:  invokevirtual   #3; // zavolat metodu wrapItUp() - opakujeme se!
   16:  aload_1
   17:  athrow              // skutečné vyhození výjimky z metody
 
   18:  return              // konec metody
                            // tabulka používaná při práci s výjimkami bude popsána v dalších kapitolách
  Exception table:
   from   to  target type
     0     4    11   any
    11    12    11   any

Blok finally se tedy v bajtkódu může opakovat, ovšem stavový prostor metody je jednodušší a lépe verifikovatelný.

4. Instrukce invokeinterface

Další prozatím nepopsanou instrukcí je instrukce nazvaná invokeinterface. Tato instrukce slouží k zavolání metody rozhraní, což sice může vypadat poněkud nesmyslně (v rozhraní mohou být pouze hlavičky metod, ale ne jejich implementace), ale v řeči tvůrců bajtkódu se tím myslí stav, kdy se volá metoda, jejíž konkrétní implementaci je možné rozeznat až v čase běhu programu (runtime). Instrukce invokeinterface se částečně podobá již popsaným instrukcím invokestatic, invokevirtual a invokespecial, ovšem s tím rozdílem, že se za operačním kódem nachází jak dvojice bajtů obsahujících index do constant poolu, tak i počet argumentů volané metody. Ve skutečnosti se však jedná o údaj, který se zde nachází z historických důvodů a čistě teoreticky by nemusel být uváděn, protože počet argumentů metody lze získat i z její signatury uložené na constant poolu. V následující tabulce jsou pro přehlednost uvedeny všechny čtyři instrukce sloužící pro zavolání různých typů metod.

# Instrukce Opkód Operandy Prováděná operace
1 invokestatic 0×B8 highbyte, lowbyte zavolání statické metody s předáním parametrů této metodě
2 invokevirtual 0×B6 highbyte, lowbyte zavolání nestatické metody s předáním hodnoty this a všech dalších parametrů
3 invokespecial 0×B7 highbyte, lowbyte zavolání speciální metody, většinou konstruktoru
4 invokeinterface 0×B9 highbyte, lowbyte, count zavolání metody deklarované v rozhraní, samozřejmě s předáním parametrů

Operandy highbyte a lowbyte tvoří šestnáctibitový index do constant poolu. Záznam na daném indexu musí být typu FieldReference, což je pro připomenutí záznam obsahující odkaz na další záznam typu Class (jméno třídy či rozhraní) a taktéž na záznam typu Name and Type (signatura metody či atributu).

Podívejme se na případ, v němž překladač musí použít metodu invokeinterface, protože v čase překladu nemůže rozhodnout o tom, metoda jaké třídy se bude ve skutečnosti volat – může se jednat o metodu compareTo() jakékoli třídy implementující rozhraní Comparable:

class Test2 {
 
    public int compare(Comparable a, Comparable b) {
        return a.compareTo(b);
    }
 
}

Vygenerovaný bajtkód vypadá následovně:

public int compare(java.lang.Comparable, java.lang.Comparable);
  Code:
   0:   aload_1                 // uložit na zásobník první (viditelný) parametr metody - objekt a
   1:   aload_2                 // uložit na zásobník druhý (viditelný) parametr metody - objekt b
   2:   invokeinterface #2,  2; // InterfaceMethod java/lang/Comparable.compareTo:(Ljava/lang/Object;)I
                                // první dvojka je index do constant poolu
                                // první dvojka je počet parametrů metody (this, b)
   7:   ireturn                 // návrat z metody compare() s návratovou hodnotou

5. Demonstrační příklad: rozdíl mezi voláním metody rozhraní a běžné virtuální metody

Z předchozího popisu nemusí být možná zcela zřejmé, ve kterých případech použije překladač instrukci invokevirtual a kdy se naopak musí uchýlit k instrukci invokeinterface. Ukažme si proto ještě jeden demonstrační příklad, v němž budou použity obě instrukce. Ve zdrojovém kódu tohoto příkladu je trojice metod, z nichž první přidává prvek do obecné kolekce, tedy do v čase překladu neznámé třídy, o níž je známo jen to, že implementuje rozhraní Collection. Ve druhé metodě se přidává prvek do kolekce typu seznam, tj. do v čase překladu neznámé třídy implementující rozhraní List a teprve v metodě třetí se prvek přidává do kolekce reprezentované známou implementací – třídou ArrayList. Všechny tři metody v demonstračním příkladu vypadají podobně, ale jejich bajtkód se bude lišit:

import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
 
class Test3 {
 
    public void addToCollection(Collection c, Object o) {
        c.add(o);
    }
 
    public void addToList(List l, Object o) {
        l.add(o);
    }
 
    public void addToArrayList(ArrayList l, Object o) {
        l.add(o);
    }
 
}

V bajtkódu metody addCollection() se musí volat metoda rozhraní, protože překladač nemůže v době překladu vědět, která konkrétní třída (resp. instance které třídy) bude této metodě v čase běhu programu předána:

public void addToCollection(java.util.Collection, java.lang.Object);
  Code:
   0:   aload_1                 // uložit první (viditelný) parametr metody na zásobník (obecná kolekce)
   1:   aload_2                 // uložit druhý (viditelný) parametr metody na zásobník (prvek)
   2:   invokeinterface #2,  2; // metoda rozhraní java/util/Collection.add:(Ljava/lang/Object;)Z
                                // Z v signatuře značí návratovou hodnotu typu boolean
   7:   pop                     // odstranit návratovou hodnotu vrácenou metodou Collection.add()
   8:   return                  // návrat z metody

Podobné je tomu i v bajtkódu metody addToList():

public void addToList(java.util.List, java.lang.Object);
  Code:
   0:   aload_1                 // uložit první (viditelný) parametr metody na zásobník (seznam)
   1:   aload_2                 // uložit druhý (viditelný) parametr metody na zásobník (prvek)
   2:   invokeinterface #3,  2; // metoda rozhraní java/util/List.add:(Ljava/lang/Object;)Z
                                // Z v signatuře značí návratovou hodnotu typu boolean
   7:   pop                     // odstranit návratovou hodnotu vrácenou metodou List.add()
   8:   return                  // návrat z metody

Teprve v metodě addToArrayList() již překladač ví, že se bude volat metoda třídy ArrayList, popř. metoda potomka této třídy. Proto může použít instrukci invokevirtual a nikoli invokeinterface:

public void addToArrayList(java.util.ArrayList, java.lang.Object);
  Code:
   0:   aload_1                 // uložit první (viditelný) parametr metody na zásobník (seznam)
   1:   aload_2                 // uložit druhý (viditelný) parametr metody na zásobník (prvek)
   2:   invokevirtual   #4;     // metoda java/util/ArrayList.add:(Ljava/lang/Object;)Z
                                // Z v signatuře značí návratovou hodnotu typu boolean
   5:   pop                     // odstranit návratovou hodnotu vrácenou metodou ArrayList.add()
   6:   return                  // návrat z metody

6. Vyhození výjimky s využitím instrukce athrow

Další důležitou instrukcí, kterou můžeme v bajtkódu často nalézt, je instrukce nazvaná athrow. Tato instrukce slouží k vyhození výjimky, přičemž při zavolání této instrukce musí být reference na objekt reprezentující výjimku uložena na vrcholu (TOS) zásobníku operandů. Tato instrukce nemá žádné operandy:

# Instrukce Opkód Operandy Prováděná operace
1 athrow 0×BF × vyhození výjimky

Bajtkód každé metody navíc může obsahovat tabulku obsahující seznam výjimek, které mohou nastat a adresy instrukcí, na něž se provede skok ve chvíli, kdy k dané výjimce skutečně dojde. Pokud však tato tabulka není přítomna, popř. neobsahuje záznam s daným typem výjimky, je výjimka vyhozena (throw) z metody a může být zachycena v nadřazených metodách, opět s využitím zmíněné tabulky.

Opět se podívejme na jednoduchý demonstrační příklad. Ten obsahuje dvě metody, z nichž jedna vyvolá obecnou výjimku typu RuntimeExcepti­on(), jejíž objekt je zkonstruován bezparametrickým konstruktorem, zatímco druhá metoda vyvolá tutéž výjimku, ovšem sestrojenou konstruktorem s parametrem typu String:

class Test4 {
 
    public static void throwRuntimeException1() {
        throw new RuntimeException();
    }
 
    public static void throwRuntimeException2() {
        throw new RuntimeException("Hello world!");
    }
 
}

Bajtkód první metody je velmi jednoduchý, protože se v něm nejdříve zavolá instrukce new pro vytvoření instance třídy RuntimeException a následně je zavolán její konstruktor. Poslední instrukce výjimku skutečně vyvolá:

public static void throwRuntimeException1();
  Code:
   0:   new     #2;         // vytvoření instance třídy java/lang/RuntimeException
   3:   dup                 // zachovat referenci pro instrukci athrow
   4:   invokespecial   #3; // konstruktor: metoda java/lang/RuntimeException."<init>":()V
   7:   athrow              // vyhození výjimky

Druhou metodu lze považovat za poněkud kuriózně pojatou variantu na program vypisující řetězec „Hello world!“:

public static void throwRuntimeException2();
  Code:
   0:   new     #2;         // vytvoření instance třídy java/lang/RuntimeException
   3:   dup                 // zachovat referenci pro instrukci athrow
   4:   ldc     #4;         // konstantní řetězec "Hello world!"
   6:   invokespecial   #5; // konstruktor: metoda java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
   9:   athrow              // vyhození výjimky

7. Synchronizace s využitím instrukcí monitorenter a monitorexit

Zajímavou dvojici instrukcí, kterou jsme si až doposud nepopsali, tvoří instrukce nazvané monitorenter a monitorexit. Jedná se o instrukce, které jsou využívány zejména pro implementaci synchronizovaných bloků, tj. bloků příkazů prováděných v daném okamžiku maximálně jedním vláknem. Princip práce obou instrukcí je založen na tom, že každému objektu je již v době jeho konstrukce přiřazen monitor (tj. jeden z typů synchronizačního primitiva), který může vlastnit pouze (lepé řečeno maximálně) jedno vlákno. Instrukce monitorenter slouží k získání tohoto monitoru s tím, že v případě, že je již monitor daného objektu vlastněn jiným vláknem, bude současné vlákno čekat na uvolnění tohoto monitoru, popř. při špatném naprogramování dojde k oblíbenému deadlocku :-). Opačný význam má instrukce monitorexit, která naopak monitor uvolní a zpřístupní tak dalším případně čekajícím vláknům. Obě popisované instrukce při svém spuštění očekávají, že se na vrcholu zásobníku operandů bude nacházet reference na objekt, jehož monitor se má při vstupu do synchronizovaného bloku použít:

# Instrukce Opkód Operandy Prováděná operace
1 monitorenter 0×C2 × vstup do synchronizovaného bloku (získání monitoru)
2 monitorexit 0×C3 × výstup ze synchronizovaného bloky (uvolnění monitoru)

Opět si ukážeme jednoduchý demonstrační příklad, který bude ilustrovat, jakým způsobem se instrukce monitorenter a monitorexit používají. Zdrojový kód tohoto příkladu je následující:

class Test5 {
 
    Object zamek;
 
    public int syncAdd(int x, int y) {
        synchronized(zamek) {
            return x + y;
        }
    }
 
    public void syncHelloWorld() {
        synchronized(zamek) {
            System.out.println("Hello world!");
        }
    }
 
}

Bajtkód metody syncAdd() obsahuje jak synchronizovaný blok, tak i – což je velmi důležité – sekvenci instrukcí, která zajistí uvolnění monitoru a to i při výskytu jakéhokoli typu výjimky:

public int syncAdd(int, int);
  Code:
   0:   aload_0             // uložení this na TOS
   1:   getfield        #2; // Field zamek:Ljava/lang/Object;
   4:   dup                 // duplikace reference na zámek
   5:   astore_3            // uložení zámku do lokální pomocné proměnné
 
   6:   monitorenter        // vstup do synchronizovaného bloku
   7:   iload_1             // uložení prvního viditelného parametru metody na zásobník
   8:   iload_2             // uložení druhého viditelného parametru metody na zásobník
   9:   iadd                // součet
   10:  aload_3             // načtení reference na zámek do TOS
   11:  monitorexit         // výstup ze synchronizovaného bloku
 
   12:  ireturn             // návratová hodnota metody je nyní na TOS
 
                            // blok instrukcí zavolaný v případě výskytu výjimky
   13:  astore  4           // meziuložení objektu reprezentujícího výjimku
   15:  aload_3             // načíst zámek (referenci na zámek)
   16:  monitorexit         // výstup ze synchronizovaného bloku
   17:  aload   4           // reference na výjimku
   19:  athrow              // skutečné vyhození výjimky
                            // blok instrukcí zavolaný v případě výskytu výjimky
  Exception table:
   from   to  target type
     7    12    13   any
    13    17    13   any

I v případě bajtkódu metody syncHelloWorld() je použita sekvence instrukcí pro uvolnění monitoru při výskytu výjimky:

public void syncHelloWorld();
  Code:
   0:   aload_0             // uložení this na TOS
   1:   getfield        #2; // Field zamek:Ljava/lang/Object;
   4:   dup                 // duplikace reference na zámek
   5:   astore_1            // uložení zámku do lokální pomocné proměnné
 
   6:   monitorenter        // vstup do synchronizovaného bloku
   7:   getstatic       #3; // atribut java/lang/System.out:Ljava/io/PrintStream;
   10:  ldc     #4;         // konstantní řetězec "Hello world!"
   12:  invokevirtual   #5; // metoda java/io/PrintStream.println:(Ljava/lang/String;)V
   15:  aload_1             // načtení reference na zámek do TOS
   16:  monitorexit         // výstup ze synchronizovaného bloku
 
   17:  goto    25          // skok na příkaz return
 
   20:  astore_2            // meziuložení objektu reprezentujícího výjimku
   21:  aload_1             // načíst zámek (referenci na zámek)
   22:  monitorexit         // výstup ze synchronizovaného bloku
   23:  aload_2             // reference na výjimku
   24:  athrow              // skutečné vyhození výjimky
 
   25:  return              // návrat z metody
 
  Exception table:
   from   to  target type
     7    17    20   any
    20    23    20   any

8. Kontrola typů za běhu: instrukce instanceof a checkcast

Posledními dvěma instrukcemi, s nimiž se v dnešním článku seznámíme, jsou instrukce nazvané instanceof a checkcast. Instrukce instanceof je přímým protějškem stejnojmenného operátoru nabízeného programovacím jazykem Java. Tento operátor a tím pádem i tato instrukce slouží ke zjištění, zda je objekt instancí dané třídy, přičemž se tento test většinou provádí až v čase běhu aplikace (JVM samozřejmě podporuje plnohodnotné RTTI – runtime type identification). Výsledkem testu je pravdivostní hodnota true popř. false. Druhá instrukce checkcast provádí velmi podobný test, ovšem s tím rozdílem, že se namísto pravdivostní hodnoty false v případě neshody typu objektu vyvolá výjimka ClassCastExcep­tion. Překladač tuto instrukci vkládá do bajtkódu zejména tehdy, pokud programátor zapíše do zdrojového kódu přetypování, které nemusí být vždy typově bezpečné.

Operační kódy instrukcí instanceof a checkcast jsou vypsány v tabulce umístěné pod tímto odstavcem:

# Instrukce Opkód Operandy Prováděná operace
1 instanceof 0×C1 highbyte, lowbyte test, zda je objekt zadaného typu
2 checkcast 0×C0 highbyte, lowbyte kontrola, zda je objekt zadaného typu

Podívejme se na demonstrační příklad obsahující dvě metody. V první metodě je použit operátor instanceof, který se přímo přeloží do stejně pojmenované instrukce. Ve druhé metodě je parametr typu Object přetypován na objekt typu String, což vede k tomu, že překladač vloží do kódu nezbytné kontroly, zda lze toto přetypování skutečně v čase běhu programu provést:

skoleni

class Test6 {
 
    public boolean isString(Object o) {
        return o instanceof String;
    }
 
    public String returnString(Object o) {
        return (String)o;
    }
 
}

Bajtkód metody isString() vypadá následovně:

public boolean isString(java.lang.Object);
  Code:
   0:   aload_1             // načtení prvního viditelného parametru metody
   1:   instanceof      #2; // test, zda se jedná o instanci java/lang/String
   4:   ireturn             // hodnota true/false je současně i návratovou hodnotou metody

V bajtkódu metody returnString můžeme vidět vloženou instrukci checkcast, jejíž zavolání může vyvolat výjimku typu ClassCastExcep­tion v případě, že metodě nebyl předán objekt typu String:

public java.lang.String returnString(java.lang.Object);
  Code:
   0:   aload_1             // načtení prvního viditelného parametru metody
   1:   checkcast       #2; // kontrola, zda se jedná o instanci java/lang/String
   4:   areturn             // pokud nedošlo k výjimce, vrátí se reference na objekt uložený na TOS

9. Odkazy na Internetu

  1. Java 6 try/finally compilation without jsr/ret
    http://cliffhac­ks.blogspot.com/2008/02­/java-6-tryfinally-compilation-without.html
  2. An empirical study of Java bytecode programs
    http://www.men­deley.com/rese­arch/an-empirical-study-of-java-bytecode-programs/
  3. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mo­bilefish.com/tu­torials/java/ja­va_quickguide_jvm_in­struction_set­.html
  4. The JVM Instruction Set
    http://mpdebo­er.home.xs4all­.nl/scriptie/no­de14.html
  5. Control Flow in the Java Virtual Machine
    http://www.ar­tima.com/under­thehood/flowP­.html
  6. Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
    http://www.ro­ot.cz/clanky/vy­uziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/
  7. Root.cz: JamVM aneb alternativa k HotSpotu nejenom pro embedded zařízení a chytré telefony
    http://www.ro­ot.cz/clanky/jam­vm-aneb-alternativa-k-hotspotu-nejenom-pro-embedded-zarizeni-tablety-a-chytre-telefony/
  8. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun­.com/docs/book­s/jvms/second_e­dition/html/VMSp­ecTOC.doc.html
  9. The class File Format
    http://java.sun­.com/docs/book­s/jvms/second_e­dition/html/Clas­sFile.doc.html
  10. javap – The Java Class File Disassembler
    http://docs.o­racle.com/java­se/1.4.2/docs/to­oldocs/window­s/javap.html
  11. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.di­e.net/man/1/j­avap-java-1.6.0-openjdk
  12. Using javap
    http://www.ide­velopment.info/da­ta/Programmin­g/java/miscella­neous_java/Usin­g_javap.html
  13. Examine class files with the javap command
    http://www.techre­public.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  14. BCEL Home page
    http://common­s.apache.org/bcel/
  15. BCEL Manual
    http://common­s.apache.org/bcel/ma­nual.html
  16. Byte Code Engineering Library (Wikipedia)
    http://en.wiki­pedia.org/wiki/BCEL
  17. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ib­m.com/developer­works/java/li­brary/j-dyn0414/
  18. Bytecode Engineering
    http://book.chi­naunix.net/spe­cial/ebook/Co­re_Java2_Volu­me2AF/0131118269/ch13lev­1sec6.html
  19. BCEL Tutorial
    http://www.smfsup­port.com/suppor­t/java/bcel-tutorial!/
  20. ASM Home page
    http://asm.ow2­.org/
  21. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2­.org/users.html
  22. ObjectWeb ASM (Wikipedia)
    http://en.wiki­pedia.org/wiki/Ob­jectWeb_ASM
  23. Java Bytecode BCEL vs ASM
    http://james.o­negoodcookie.com/2005/10­/26/java-bytecode-bcel-vs-asm/
  24. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2­.org/eclipse/in­dex.html
  25. aspectj (Eclipse)
    http://www.eclip­se.org/aspectj/
  26. Aspect-oriented programming (Wikipedia)
    http://en.wiki­pedia.org/wiki/As­pect_oriented_pro­gramming
  27. AspectJ (Wikipedia)
    http://en.wiki­pedia.org/wiki/As­pectJ
  28. EMMA: a free Java code coverage tool
    http://emma.sou­rceforge.net/
  29. Cobertura
    http://cobertu­ra.sourceforge­.net/
  30. FindBugs
    http://findbug­s.sourceforge­.net/
  31. GNU Classpath
    www.gnu.org/s/clas­spath/
  32. Java VMs Compared
    http://bugblog­ger.com/java-vms-compared-160/
  33. JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
    http://www.jcp­.org/en/jsr/de­tail?id=223
  34. Scripting for the Java Platform
    http://java.sun­.com/developer/techni­calArticles/J2SE/Des­ktop/scriptin­g/
  35. Scripting for the Java Platform (Wikipedia)
    http://en.wiki­pedia.org/wiki/Scrip­ting_for_the_Ja­va_Platform
  36. Java Community Process
    http://en.wiki­pedia.org/wiki/Ja­va_Specificati­on_Request
  37. Java HotSpot VM Options
    http://www.ora­cle.com/technet­work/java/java­se/tech/vmopti­ons-jsp-140102.html
  38. Great Computer Language Shootout
    http://c2.com/cgi/wi­ki?GreatCompu­terLanguageSho­otout
  39. Java performance
    http://en.wiki­pedia.org/wiki/Ja­va_performance
  40. Trying the prototype
    http://mail.o­penjdk.java.net/pi­permail/lambda-dev/2010-August/002179.html
  41. Better closures (for Java)
    http://blogs.sun­.com/jrose/en­try/better_clo­sures
  42. Lambdas in Java: An In-Depth Analysis
    http://www.in­foq.com/articles/lam­bdas-java-analysis
  43. Class ReflectiveOpe­rationExcepti­on
    http://downlo­ad.java.net/jdk7/doc­s/api/java/lan­g/ReflectiveO­perationExcep­tion.html
  44. Proposal: Indexing access syntax for Lists and Maps
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/001108.html
  45. Proposal: Elvis and Other Null-Safe Operators
    http://mail.o­penjdk.java.net/pi­permail/coin-dev/2009-March/000047.html
  46. Java 7 : Oracle pushes a first version of closures
    http://www.bap­tiste-wicht.com/2010/05­/oracle-pushes-a-first-version-of-closures/
  47. Groovy: An agile dynamic language for the Java Platform
    http://groovy­.codehaus.org/O­perators
  48. Better Strategies for Null Handling in Java
    http://www.sli­deshare.net/Step­han.Schmidt/bet­ter-strategies-for-null-handling-in-java
  49. Control Flow in the Java Virtual Machine
    http://www.ar­tima.com/under­thehood/flowP­.html
  50. Java Virtual Machine
    http://en.wiki­pedia.org/wiki/Ja­va_virtual_machi­ne
  51. ==, .equals(), compareTo(), and compare()
    http://leepoin­t.net/notes-java/data/expres­sions/22compa­reobjects.html
  52. New JDK7 features
    http://openjdk­.java.net/pro­jects/jdk7/fe­atures/
  53. Project Coin: Bringing it to a Close(able)
    http://blogs.sun­.com/darcy/en­try/project_co­in_bring_close
  54. CloseableFinder source code
    http://blogs.sun­.com/darcy/re­source/Projec­tCoin/Closeable­Finder.java
  55. Joe Darcy blog about JDK
    http://blogs.sun­.com/darcy
  56. Java 7 – more dynamics
    http://www.bap­tiste-wicht.com/2010/04­/java-7-more-dynamics/
  57. New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
    http://java.sun­.com/developer/techni­calArticles/Dyn­TypeLang/index­.html

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.