Obsah
1. Pohled pod kapotu JVM – volání funkcí a metod v JVM, Lua VM a Python VM
2. Volání metod v bajtkódu programovacího jazyka Java (JVM)
4. Demonstrační příklad Test27.java: použití instrukce invokestatic
6. Demonstrační příklad Test28.java: použití instrukce invokevirtual
8. Demonstrační příklad Test29.java: použití instrukce invokespecial
10. Demonstrační příklad Test30.java: použití instrukce invokeinterface
11. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů
1. Pohled pod kapotu JVM – volání funkcí a metod v JVM, Lua VM a Python VM
Jednou z posledních a prozatím nepopsaných oblastí souvisejících s během programů ve virtuálních strojích jazyků Java, Lua i Python, je volání funkcí popř. metod. Jedná se samozřejmě o velmi často prováděnou činnost, při níž je kromě vlastního zavolání vybrané funkce nebo metody nutné zajistit předání hodnot parametrů a taktéž zpracování návratové hodnoty (Java) či většího množství návratových hodnot (Lua). V dnešním článku se zaměříme především na popis volání metod v bajtkódu programovacího jazyka Java, neboť zde je situace asi nejsložitější, a to mj. i kvůli silnému typovému systému Javy a s ním související nutnosti znát u každé metody přesný počet a taktéž typ všech jejích parametrů. V navazující části tohoto seriálu si pak možnosti Javy a především JVM porovnáme s možnostmi programovacích jazyků Lua a Python i jejich virtuálních strojů (Lua VM a Python VM).
2. Volání metod v bajtkódu programovacího jazyka Java (JVM)
Virtuální stroj programovacího jazyka Java obsahuje čtyři (resp. dnes již pět) instrukcí bajtkódu určených pro volání metod různých typů. Tyto instrukce se jmenují invokestatic, invokevirtual, invokespecial a invokeinterface. Nově byla do bajtkódu JVM přidána i instrukce invokedynamic, která má význam především při implementaci dynamicky typovaných programovacích jazyků nad JVM. V dalším textu se budeme zabývat především prvními čtyřmi zmíněnými instrukcemi. Na rozdíl od instrukcí JVM popsaných v předchozích článcích jsou všechny čtyři instrukce invoke* zvláštní tím, že pracují se signaturami metod uloženými v constant poolu. Díky tomu mohou být u instrukcí uvedeny jen šestnáctibitové indexy do constant poolu a nikoli plná jména metod a jejich typů, což mj. znamená výrazné zkrácení celého bajtkódu (a taktéž snazší a rychlejší práci JIT překladače):
# | 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í privátní metody popř. konstruktoru |
4 | invokeinterface | 0×B9 | highbyte, lowbyte, count | zavolání metody deklarované v rozhraní, samozřejmě s předáním parametrů |
3. Instrukce invokestatic
Instrukce bajtkódu JVM nazvaná invokestatic slouží, jak již ostatně název této instrukce napovídá, k zavolání statické metody, tj. takové metody, kterou je možné zavolat i v případě, že neexistuje žádná instance třídy, v níž je tato metoda deklarována (samozřejmě za předpokladu, že jsou vhodně nastavena přístupová práva k metodě, to je kontrolováno jak v době překladu, tak i v době běhu aplikace). Tato instrukce pracuje se signaturami metod uložených v constant poolu. Před zavoláním statické metody je nutné na zásobník operandů uložit všechny parametry volané metody a po zavolání metody a návratu z ní se na tomtéž zásobníku bude nacházet návratová hodnota (samozřejmě za předpokladu, že se nejedná o metodu s návratovou hodnotou void). Žádné další hodnoty se volané metodě nepředávají. Formát instrukce invokestatic je následující:
# | 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ě |
Operandy highbyte a lowbyte tvoří šestnáctibitový index do constant poolu. Záznam uložený na daném indexu musí být typu MethodReference, což je pro připomenutí záznam obsahující odkaz na záznam typu Class (jméno třídy) a taktéž na záznam typu Name and Type (signatura metody či atributu). JVM tedy ví, kterou metodu má zavolat (nemusí tedy pracně párovat datové typy parametrů s typy argumentů atd.), ovšem přesnou adresu metody vyhodnocuje až v době běhu aplikace.
4. Demonstrační příklad Test27.java: použití instrukce invokestatic
Podívejme se nyní na velmi jednoduchý demonstrační příklad nazvaný Test27.java. V tomto příkladu jsou deklarovány čtyři statické metody pojmenované staticMethod1() až staticMethod4(), které jsou následně volány z (nestatických) metod callStaticMethod1() až callStaticMethod4(). Navíc se v metodě callStaticMethod5() volá Math.abs(), tj. metoda náležející jiné třídě:
/** * Demonstracni priklad cislo 27. * * Vyuziti instrukce invokestatic. */ public class Test27 { /** * Staticka bezparametricka metoda. */ static void staticMethod1() { // nic = prazdne telo metody } /** * Staticka bezparametricka metoda vracejici cele cislo. */ static int staticMethod2() { return 42; } /** * Staticka metoda se dvema parametry. */ static void staticMethod3(int x, int y) { // nic = prazdne telo metody } /** * Staticka metoda se dvema parametry vracejici cele cislo. */ static int staticMethod4(int x, int y) { return x + y; } /** * Volani staticke metody ze stejne tridy. */ void callStaticMethod1() { staticMethod1(); } /** * Volani staticke metody ze stejne tridy. */ void callStaticMethod2() { staticMethod2(); } /** * Volani staticke metody ze stejne tridy. */ void callStaticMethod3() { staticMethod3(1, 2); } /** * Volani staticke metody ze stejne tridy. */ void callStaticMethod4() { staticMethod4(3, 4); } /** * Volani staticke metody ze tridy Math. */ void callStaticMethod5() { Math.abs(42); } }
Čtyři volané statické metody staticMethod1() až staticMethod4() jsou do bajtkódu přeloženy následujícím způsobem:
static void staticMethod1(); Code: 0: return // vyskok z metody bez pouziti navratove hodnoty static int staticMethod2(); Code: 0: bipush 42 // ulozit na vrchol zasobniku operandu konstantu 42 2: ireturn // vyskok z metody s navratovou hodnotou ziskanou z TOS static void staticMethod3(int, int); Code: 0: return // vyskok z metody bez pouziti navratove hodnoty static int staticMethod4(int, int); Code: 0: iload_0 // ulozit na vrchol zasobniku operandu hodnotu prvniho parametru metody 1: iload_1 // ulozit na vrchol zasobniku operandu hodnotu druheho parametru metody 2: iadd // secist hodnotu obou parametru a vratit vysledek zpet na TOS 3: ireturn // vyskok z metody s navratovou hodnotou ziskanou z TOS
Volání těchto metod je v bajtkódu provedeno instrukcí invokestatic:
void callStaticMethod1(); Code: 0: invokestatic #2; // volani staticke metody: Method staticMethod1:()V 3: return // vyskok z metody bez pouziti navratove hodnoty void callStaticMethod2(); Code: 0: invokestatic #3; // volani staticke metody: Method staticMethod2:()I 3: pop // odstranit navratovou hodnotu ze zasobniku operandu 4: return // vyskok z metody bez pouziti navratove hodnoty void callStaticMethod3(); Code: 0: iconst_1 // ulozit na zasobnik operandu hodnotu prvniho argumentu volane metody 1: iconst_2 // ulozit na zasobnik operandu hodnotu druheho argumentu volane metody 2: invokestatic #4; // volani staticke metody: Method staticMethod3:(II)V 5: return // vyskok z metody bez pouziti navratove hodnoty void callStaticMethod4(); Code: 0: iconst_3 // ulozit na zasobnik operandu hodnotu prvniho argumentu volane metody 1: iconst_4 // ulozit na zasobnik operandu hodnotu druheho argumentu volane metody 2: invokestatic #5; // volani staticke metody: Method staticMethod4:(II)I 5: pop // odstranit navratovou hodnotu ze zasobniku operandu 6: return // vyskok z metody bez pouziti navratove hodnoty
Stejnou instrukcí je zajištěno i volání metody Math.abs():
void callStaticMethod5(); Code: 0: bipush 42 // ulozit na zasobnik operandu hodnotu jedineho argumentu volane metody 2: invokestatic #6; // volani staticke metody: Method java/lang/Math.abs:(I)I 5: pop // odstranit navratovou hodnotu ze zasobniku operandu 6: return // vyskok z metody bez pouziti navratove hodnoty
U instrukcí invokestatic byly jako parametry použity indexy #2 až #6 ukazující na záznamy typu MethodRef v constant poolu:
Constant pool: #1 = Methodref #8.#27; // java/lang/Object."init":()V #2 = Methodref #7.#28; // Test27.staticMethod1:()V #3 = Methodref #7.#29; // Test27.staticMethod2:()I #4 = Methodref #7.#30; // Test27.staticMethod3:(II)V #5 = Methodref #7.#31; // Test27.staticMethod4:(II)I #6 = Methodref #32.#33; // java/lang/Math.abs:(I)I #7 = Class #34; // Test27 #8 = Class #35; // java/lang/Object #9 = Utf8 init; #10 = Utf8 ()V; #11 = Utf8 Code; #12 = Utf8 LineNumberTable; #13 = Utf8 staticMethod1; #14 = Utf8 staticMethod2; #15 = Utf8 ()I; #16 = Utf8 staticMethod3; #17 = Utf8 (II)V; #18 = Utf8 staticMethod4; #19 = Utf8 (II)I; #20 = Utf8 callStaticMethod1; #21 = Utf8 callStaticMethod2; #22 = Utf8 callStaticMethod3; #23 = Utf8 callStaticMethod4; #24 = Utf8 callStaticMethod5; #25 = Utf8 SourceFile; #26 = Utf8 Test27.java; #27 = NameAndType #9:#10; // "init":()V #28 = NameAndType #13:#10; // staticMethod1:()V #29 = NameAndType #14:#15; // staticMethod2:()I #30 = NameAndType #16:#17; // staticMethod3:(II)V #31 = NameAndType #18:#19; // staticMethod4:(II)I #32 = Class #36; // java/lang/Math #33 = NameAndType #37:#38; // abs:(I)I #34 = Utf8 Test27; #35 = Utf8 java/lang/Object; #36 = Utf8 java/lang/Math; #37 = Utf8 abs; #38 = Utf8 (I)I;
Podívejme se nyní na to, jakým konkrétním způsobem je v constant poolu uložena signatura metody Test27.staticMethod3(). Tato signatura se nachází na indexu #4, kde je však uložena pouze dvojice indexů #7 a #30. První index odkazuje na jméno třídy, druhý index na záznam typu NameAndType, kde je (opět nepřímo) uloženo jméno metody i typy jejích parametrů a typ návratové hodnoty:
+-+--- #4 = Methodref #7.#30; // Test27.staticMethod3:(II)V | | | +--> #7 = Class #34;-----+ // Test27 | | +----> #30 = NameAndType #16:#17; | // staticMethod3:(II)V | | | #16 = Utf8 <--------------+ | | "staticMethod3;" #17 = Utf8 <------------------+ | "(II)V;" | #34 = Utf8 <----------------------+ "Test27;"
5. Instrukce invokevirtual
V případě instrukce invokevirtual je situace poněkud složitější, než u výše popsané instrukce invokestatic, protože virtuální stroj Javy musí zjistit, metoda které třídy má být ve skutečnosti zavolána – může se totiž jednat o metodu předka, a to v libovolné úrovni (klidně se může jednat i o metodu prapředka všech tříd – třídy Object). Proto virtuální stroj Javy rekurzivně prochází předky třídy, jejíž instance je uložena na zásobníku operandů a hledá metodu se signaturou stejnou, jako je signatura uložená v constant poolu. Jakmile je taková metoda nalezena, je rekurzivní vyhledávání ihned ukončeno a nalezená metoda je zavolána se všemi parametry, včetně skrytého parametru představujícího this (referenci na objekt, pro nějž je metoda zavolána). Z tohoto důvodu v demonstračním příkladu popsaném v navazující kapitole budou mít všechny testovací metody jeden skrytý parametr, který musí být před voláním nestatické metody naplněn (předávání parametrů se opět provádí přes zásobník operandů).
6. Demonstrační příklad Test28.java: použití instrukce invokevirtual
V dnešním druhém demonstračním příkladu Test28.java jsou deklarovány čtyři nestatické metody nonStaticMethod1() až nonStaticMethod4(), které jsou následně volány z testovacích metod callnonStaticMethod1() až callNonStaticMethod4(). V páté testovací metodě callNonStaticMethod5() je volána další nestatická metoda getClass(), jenž je ovšem deklarována v předkovi třídy Test28:
/** * Demonstracni priklad cislo 28. * * Vyuziti instrukce invokevirtual. */ public class Test28 { /** * Nestaticka bezparametricka metoda. */ void nonStaticMethod1() { // nic = prazdne telo metody } /** * Nestaticka bezparametricka metoda vracejici cele cislo. */ int nonStaticMethod2() { return 42; } /** * Nestaticka metoda se dvema parametry. */ void nonStaticMethod3(int x, int y) { // nic = prazdne telo metody } /** * Nestaticka metoda se dvema parametry vracejici cele cislo. */ int nonStaticMethod4(int x, int y) { return x + y; } /** * Volani nestaticke metody ze stejne tridy. */ void callnonStaticMethod1() { nonStaticMethod1(); } /** * Volani nestaticke metody ze stejne tridy. */ void callnonStaticMethod2() { nonStaticMethod2(); } /** * Volani nestaticke metody ze stejne tridy. */ void callnonStaticMethod3() { nonStaticMethod3(1, 2); } /** * Volani nestaticke metody ze stejne tridy. */ void callnonStaticMethod4() { nonStaticMethod4(3, 4); } /** * Volani nestaticke metody ze stejne tridy. */ void callnonStaticMethod5() { getClass(); } }
Bajtkódy přeložených metod nonStaticMethod1() až nonStaticMethod4() vypadají následovně. Zajímavý je především bajtkód poslední zmíněné metody, protože je zde patrné „posunutí“ indexů parametrů kvůli tomu, že se na pozici prvního parametru nachází this:
void nonStaticMethod1(); Code: 0: return // vyskok z metody bez pouziti navratove hodnoty int nonStaticMethod2(); Code: 0: bipush 42 // ulozit na vrchol zasobniku operandu konstantu 42 2: ireturn // vyskok z metody s navratovou hodnotou ziskanou z TOS void nonStaticMethod3(int, int); Code: 0: return // vyskok z metody bez pouziti navratove hodnoty int nonStaticMethod4(int, int); Code: 0: iload_1 // ulozit na vrchol zasobniku operandu hodnotu prvniho parametru metody 1: iload_2 // ulozit na vrchol zasobniku operandu hodnotu druheho parametru metody 2: iadd // secist hodnotu obou parametru a vratit vysledek zpet na TOS 3: ireturn // vyskok z metody s navratovou hodnotou ziskanou z TOS
Při volání všech metod je použita instrukce invokevirtual, přičemž se vždy před voláním uloží na zásobník operandů i hodnota this a to konkrétně s použitím instrukce aload0:
void callnonStaticMethod1(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: invokevirtual #2; // volani virtualni metody: Method nonStaticMethod1:()V 4: return // vyskok z metody bez pouziti navratove hodnoty void callnonStaticMethod2(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: invokevirtual #3; // volani virtualni metody: Method nonStaticMethod2:()I 4: pop // odstranit navratovou hodnotu ze zasobniku operandu 5: return // vyskok z metody bez pouziti navratove hodnoty void callnonStaticMethod3(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: iconst_1 // ulozit na zasobnik operandu hodnotu prvniho argumentu volane metody 2: iconst_2 // ulozit na zasobnik operandu hodnotu druheho argumentu volane metody 3: invokevirtual #4; // volani virtualni metody: Method nonStaticMethod3:(II)V 6: return // vyskok z metody bez pouziti navratove hodnoty void callnonStaticMethod4(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: iconst_3 // ulozit na zasobnik operandu hodnotu prvniho argumentu volane metody 2: iconst_4 // ulozit na zasobnik operandu hodnotu druheho argumentu volane metody 3: invokevirtual #5; // volani virtualni metody: Method nonStaticMethod4:(II)I 6: pop // odstranit navratovou hodnotu ze zasobniku operandu 7: return // vyskok z metody bez pouziti navratove hodnoty
Zcela stejným způsobem, tj. přes instrukci invokevirtual, je zavolána i metoda předka:
void callnonStaticMethod5(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: invokevirtual #6; // volani virtualni metody: Method java/lang/Object.getClass:()Ljava/lang/Class; 4: pop // odstranit navratovou hodnotu ze zasobniku operandu 5: return // vyskok z metody bez pouziti navratove hodnoty
Pro úplnost doplňme strukturu constant poolu, zejména záznamů na indexech #2 až #6, kde jsou uloženy signatury volaných metod:
#1 = Methodref #8.#27; // java/lang/Object."init":()V #2 = Methodref #7.#28; // Test28.nonStaticMethod1:()V #3 = Methodref #7.#29; // Test28.nonStaticMethod2:()I #4 = Methodref #7.#30; // Test28.nonStaticMethod3:(II)V #5 = Methodref #7.#31; // Test28.nonStaticMethod4:(II)I #6 = Methodref #8.#32; // java/lang/Object.getClass:()Ljava/lang/Class; #7 = Class #33; // Test28 #8 = Class #34; // java/lang/Object #9 = Utf8 init; #10 = Utf8 ()V; #11 = Utf8 Code; #12 = Utf8 LineNumberTable; #13 = Utf8 nonStaticMethod1; #14 = Utf8 nonStaticMethod2; #15 = Utf8 ()I; #16 = Utf8 nonStaticMethod3; #17 = Utf8 (II)V; #18 = Utf8 nonStaticMethod4; #19 = Utf8 (II)I; #20 = Utf8 callnonStaticMethod1; #21 = Utf8 callnonStaticMethod2; #22 = Utf8 callnonStaticMethod3; #23 = Utf8 callnonStaticMethod4; #24 = Utf8 callnonStaticMethod5; #25 = Utf8 SourceFile; #26 = Utf8 Test28.java; #27 = NameAndType #9:#10; // "init":()V #28 = NameAndType #13:#10; // nonStaticMethod1:()V #29 = NameAndType #14:#15; // nonStaticMethod2:()I #30 = NameAndType #16:#17; // nonStaticMethod3:(II)V #31 = NameAndType #18:#19; // nonStaticMethod4:(II)I #32 = NameAndType #35:#36; // getClass:()Ljava/lang/Class; #33 = Utf8 Test28; #34 = Utf8 java/lang/Object; #35 = Utf8 getClass; #36 = Utf8 ()Ljava/lang/Class;;
7. Instrukce invokespecial
Další instrukcí určenou v bajtkódu JVM pro volání metod, je instrukce nazvaná invokespecial. Tato instrukce se používá zejména při inicializaci objektu, konkrétně při volání konstruktoru předka poté, co je objekt vytvořen, ale taktéž při volání privátních metod, popř. překrytých metod. Struktura instrukce invokespecial je vypsána v následující tabulce:
# | Instrukce | Opkód | Operandy | Prováděná operace |
---|---|---|---|---|
1 | invokespecial | 0×B7 | highbyte, lowbyte | zavolání privátní metody popř. konstruktoru |
Formát této instrukce je tedy totožný s formátem instrukcí invokestatic i invokevirtual.
8. Demonstrační příklad Test29.java: použití instrukce invokespecial
Jak jsme si již řekli v předchozí kapitole, má instrukce invokespecial hned několik možností využití. První z nich je volání privátní metody. Instrukce invokespecial se využívá taktéž při inicializaci objektů. Oba způsoby použití této instrukce budou ilustrovány na demonstračním příkladu Test29:
/** * Demonstracni priklad cislo 29. * * Vyuziti instrukce invokespecial. */ public class Test29 { /** * Privatni staticka bezparametricka metoda. */ private void nonStaticPrivateMethod1() { // nic = prazdne telo metody } /** * Privatni staticka bezparametricka metoda vracejici cele cislo. */ private int nonStaticPrivateMethod2() { return 42; } /** * Privatni staticka metoda se dvema parametry. */ private void nonStaticPrivateMethod3(int x, int y) { // nic = prazdne telo metody } /** * Privatni staticka metoda se dvema parametry vracejici cele cislo. */ private int nonStaticPrivateMethod4(int x, int y) { return x + y; } /** * Volani privatni staticke metody ze stejne tridy. */ void callNonStaticPrivateMethod1() { nonStaticPrivateMethod1(); } /** * Volani privatni staticke metody ze stejne tridy. */ void callNonStaticPrivateMethod2() { nonStaticPrivateMethod2(); } /** * Volani privatni staticke metody ze stejne tridy. */ void callNonStaticPrivateMethod3() { nonStaticPrivateMethod3(1, 2); } /** * Volani privatni staticke metody ze stejne tridy. */ void callNonStaticPrivateMethod4() { nonStaticPrivateMethod4(3, 4); } /** * Volani konstruktoru. */ void callConstructor() { new Test29(); } }
Překlad metod nonStaticPrivateMethod1 až nonStaticPrivateMethod4 do bajtkódu JVM je vlastně stejný s překladem, který jsme již viděli u předchozího demonstračního příkladu:
private void nonStaticPrivateMethod1(); Code: 0: return // vyskok z metody bez pouziti navratove hodnoty private int nonStaticPrivateMethod2(); Code: 0: bipush 42 // ulozit na vrchol zasobniku operandu konstantu 42 2: ireturn // vyskok z metody s navratovou hodnotou ziskanou z TOS private void nonStaticPrivateMethod3(int, int); Code: 0: return // vyskok z metody bez pouziti navratove hodnoty private int nonStaticPrivateMethod4(int, int); Code: 0: iload_1 // ulozit na vrchol zasobniku operandu hodnotu prvniho parametru metody 1: iload_2 // ulozit na vrchol zasobniku operandu hodnotu druheho parametru metody 2: iadd // secist hodnotu obou parametru a vratit vysledek zpet na TOS 3: ireturn // vyskok z metody s navratovou hodnotou ziskanou z TOS
Zajímavější je až volání těchto metod s využitím instrukce invokespecial:
void callNonStaticPrivateMethod1(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: invokespecial #2; // volani nestaticke privatni metody: Method nonStaticPrivateMethod1:()V 4: return // vyskok z metody bez pouziti navratove hodnoty void callNonStaticPrivateMethod2(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: invokespecial #3; // volani nestaticke privatni metody: Method nonStaticPrivateMethod2:()I 4: pop // odstranit navratovou hodnotu ze zasobniku operandu 5: return // vyskok z metody bez pouziti navratove hodnoty void callNonStaticPrivateMethod3(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: iconst_1 // ulozit na zasobnik operandu hodnotu prvniho argumentu volane metody 2: iconst_2 // ulozit na zasobnik operandu hodnotu druheho argumentu volane metody 3: invokespecial #4; // volani nestaticke privatni metody: Method nonStaticPrivateMethod3:(II)V 6: return // vyskok z metody bez pouziti navratove hodnoty void callNonStaticPrivateMethod4(); Code: 0: aload_0 // ulozit na vrchol zasobniku operandu hodnotu this 1: iconst_3 // ulozit na zasobnik operandu hodnotu prvniho argumentu volane metody 2: iconst_4 // ulozit na zasobnik operandu hodnotu druheho argumentu volane metody 3: invokespecial #5; // volani nestaticke privatni metody: Method nonStaticPrivateMethod4:(II)I 6: pop // odstranit navratovou hodnotu ze zasobniku operandu 7: return // vyskok z metody bez pouziti navratove hodnoty
V bajtkódu metody callConstructor() se nejdříve pomocí instrukce new vytvoří instance třídy Test29 a posléze se zavolá speciální metoda nazvaná <init>, která (poněkud zjednodušeně řečeno) odpovídá konstruktoru této třídy. Povšimněte si také dvojice instrukcí dup a pop na adresách 3 a 7. Tyto instrukce slouží pro úschovu a následné odstranění reference na objekt vytvořený pomocí new, i když ve skutečnosti není tato reference nikde použita (instrukce invokespecial taktéž využívá referenci na vytvořený objekt, ovšem tato reference je při zpracování této instrukce z TOS odstraněna):
void callConstructor(); Code: 0: new #6; // vytvoreni nove instance tridy Test29 3: dup // duplikace reference na nove vytvoreny objekt 4: invokespecial #7; // volani tela konstruktoru 7: pop // odstranit navratovou hodnotu ze zasobniku operandu 8: return // vyskok z metody bez pouziti navratove hodnoty
Opět se podívejme ještě na strukturu constant poolu třídy Test29:
#1 = Methodref #8.#27; // java/lang/Object."init":()V #2 = Methodref #6.#28; // Test29.nonStaticPrivateMethod1:()V #3 = Methodref #6.#29; // Test29.nonStaticPrivateMethod2:()I #4 = Methodref #6.#30; // Test29.nonStaticPrivateMethod3:(II)V #5 = Methodref #6.#31; // Test29.nonStaticPrivateMethod4:(II)I #6 = Class #32; // Test29 #7 = Methodref #6.#27; // Test29."init":()V #8 = Class #33; // java/lang/Object #9 = Utf8 ; #10 = Utf8 ()V; #11 = Utf8 Code; #12 = Utf8 LineNumberTable; #13 = Utf8 nonStaticPrivateMethod1; #14 = Utf8 nonStaticPrivateMethod2; #15 = Utf8 ()I; #16 = Utf8 nonStaticPrivateMethod3; #17 = Utf8 (II)V; #18 = Utf8 nonStaticPrivateMethod4; #19 = Utf8 (II)I; #20 = Utf8 callNonStaticPrivateMethod1; #21 = Utf8 callNonStaticPrivateMethod2; #22 = Utf8 callNonStaticPrivateMethod3; #23 = Utf8 callNonStaticPrivateMethod4; #24 = Utf8 callConstructor; #25 = Utf8 SourceFile; #26 = Utf8 Test29.java; #27 = NameAndType #9:#10; // "init":()V #28 = NameAndType #13:#10; // nonStaticPrivateMethod1:()V #29 = NameAndType #14:#15; // nonStaticPrivateMethod2:()I #30 = NameAndType #16:#17; // nonStaticPrivateMethod3:(II)V #31 = NameAndType #18:#19; // nonStaticPrivateMethod4:(II)I #32 = Utf8 Test29; #33 = Utf8 java/lang/Object;
9. Instrukce invokeinterface
Další (dnes již poslední) 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), ovšem 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:
1 | invokeinterface | 0×B9 | highbyte, lowbyte, count | zavolání metody deklarované v rozhraní, samozřejmě s předáním parametrů |
Operandy highbyte a lowbyte opět tvoří šestnáctibitový index do constant poolu. Záznam na daném indexu musí být typu InterfaceMethodref (nikoli MethodRef), 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).
10. Demonstrační příklad Test30.java: použití instrukce invokeinterface
Podívejme se nyní na demonstrační případ, v němž překladač musí použít při překladu do bajtkódu instrukce 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 (v prvním případě) jednat o metodu compareTo() jakékoli třídy implementující rozhraní Comparable, to stejné platí i o další metody iterator() a append():
/** * Demonstracni priklad cislo 30. * * Vyuziti instrukce invokeinterface pro volani metod, jejichz * hlavicky jsou predepsany v nejakem (standardnim) rozhrani. */ public class Test30 { /** * Zavolani metody Iterable.iterator(). */ public java.util.Iterator getIterator(Iterable i) { return i.iterator(); } /** * Zavolani metody Comparable.compareTo(). */ public int compare(Comparable a, Comparable b) { return a.compareTo(b); } /** * Zavolani metody Appendable.append(char). */ public void append(Appendable a, char c) throws java.io.IOException { a.append(c); } }
Vygenerovaný bajtkód metod getIterator(), compare() a append() vypadá následovně:
public java.util.Iterator getIterator(java.lang.Iterable); Code: 0: aload_1 // ulozit na zasobnik operandu prvni (viditelny) parametr metody // zavolani metody rozhrani 1: invokeinterface #2, 1; // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator; 6: areturn // vyskok z metody, reference na iterator je vracena volajicimu kodu public int compare(java.lang.Comparable, java.lang.Comparable); Code: 0: aload_1 // ulozit na zasobnik operandu referenci na prvni objekt typu Comparable 1: aload_2 // ulozit na zasobnik operandu referenci na druhy objekt typu Comparable // zavolani metody rozhrani 2: invokeinterface #3, 2; // InterfaceMethod java/lang/Comparable.compareTo:(Ljava/lang/Object;)I 7: ireturn // vratit vysledek volani Comparable.compareTo() public void append(java.lang.Appendable, char) throws java.io.IOException; Code: 0: aload_1 // ulozit na zasobnik operandu referenci na objekt typu Appendable 1: iload_2 // ulozit na zasobnik operandu znak // zavolani metody rozhrani 2: invokeinterface #4, 2; // InterfaceMethod java/lang/Appendable.append:(C)Ljava/lang/Appendable; 7: pop // odstranit navratovou hodnotu metody Appendable.append() // (coz je opet objekt implementujici rozhrani Appendable) 8: return // vyskok z metody bez pouziti navratove hodnoty
Pro úplnost si doplňme výpis constant poolu třídy Test30. Nejdůležitější jsou zde záznamy uložené na indexech #2, #3 a #4, které jsou typu InterfaceMethodref:
#1 = Methodref #6.#21; // java/lang/Object."init":()V #2 = InterfaceMethodref #22.#23; // java/lang/Iterable.iterator:()Ljava/util/Iterator; #3 = InterfaceMethodref #24.#25; // java/lang/Comparable.compareTo:(Ljava/lang/Object;)I #4 = InterfaceMethodref #26.#27; // java/lang/Appendable.append:(C)Ljava/lang/Appendable; #5 = Class #28; // Test30 #6 = Class #29; // java/lang/Object #7 = Utf8 init; #8 = Utf8 ()V; #9 = Utf8 Code; #10 = Utf8 LineNumberTable; #11 = Utf8 getIterator; #12 = Utf8 (Ljava/lang/Iterable;)Ljava/util/Iterator;; #13 = Utf8 compare; #14 = Utf8 (Ljava/lang/Comparable;Ljava/lang/Comparable;)I; #15 = Utf8 append; #16 = Utf8 (Ljava/lang/Appendable;C)V; #17 = Utf8 Exceptions; #18 = Class #30; // java/io/IOException #19 = Utf8 SourceFile; #20 = Utf8 Test30.java; #21 = NameAndType #7:#8; // "init":()V #22 = Class #31; // java/lang/Iterable #23 = NameAndType #32:#33; // iterator:()Ljava/util/Iterator; #24 = Class #34; // java/lang/Comparable #25 = NameAndType #35:#36; // compareTo:(Ljava/lang/Object;)I #26 = Class #37; // java/lang/Appendable #27 = NameAndType #15:#38; // append:(C)Ljava/lang/Appendable; #28 = Utf8 Test30; #29 = Utf8 java/lang/Object; #30 = Utf8 java/io/IOException; #31 = Utf8 java/lang/Iterable; #32 = Utf8 iterator; #33 = Utf8 ()Ljava/util/Iterator;; #34 = Utf8 java/lang/Comparable; #35 = Utf8 compareTo; #36 = Utf8 (Ljava/lang/Object;)I; #37 = Utf8 java/lang/Appendable; #38 = Utf8 (C)Ljava/lang/Appendable;;
11. Repositář se zdrojovými kódy všech čtyř dnešních demonstračních příkladů
Všechny čtyři dnes popsané a použité demonstrační příklady byly uloženy do Mercurial repositáře umístěného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. Odkazy na prozatím poslední verze těchto čtyř příkladů naleznete v tabulce pod tímto odstavcem:
12. Odkazy na Internetu
- Python Byte Code Instructions
https://docs.python.org/release/2.5.2/lib/bytecodes.html - For-each Loop in Java
http://www.leepoint.net/notes-java/flow/loops/foreach.html - Programming in Lua (first edition)
http://www.lua.org/pil/contents.html - Programming in Lua: Numeric for
http://www.lua.org/pil/4.3.4.html - Programming in Lua: break and return
http://www.lua.org/pil/4.4.html - Programming in Lua: Tables
http://www.lua.org/pil/2.5.html - Programming in Lua: Table Constructors
http://www.lua.org/pil/3.6.html - Programovací jazyk Lua
http://palmknihy.cz/web/kniha/programovaci-jazyk-lua-12651.htm - Lua: Tables Tutorial
http://lua-users.org/wiki/TablesTutorial - Python 2.x: funkce range()
https://docs.python.org/2/library/functions.html#range - Python 2.x: typ iterátor
https://docs.python.org/2/library/stdtypes.html#iterator-types - Lua: Control Structure Tutorial
http://lua-users.org/wiki/ControlStructureTutorial - Lua Types Tutorial
http://lua-users.org/wiki/LuaTypesTutorial - Goto Statement in Lua
http://lua-users.org/wiki/GotoStatement - Python break, continue and pass Statements
http://www.tutorialspoint.com/python/python_loop_control.htm - For Loop (Wikipedia)
http://en.wikipedia.org/wiki/For_loop - Heinz Rutishauser
http://en.wikipedia.org/wiki/Heinz_Rutishauser - Parrot
http://www.parrot.org/ - Parrot languages
http://www.parrot.org/languages - Parrot Primer
http://docs.parrot.org/parrot/latest/html/docs/intro.pod.html - Parrot Opcodes
http://docs.parrot.org/parrot/latest/html/ops.html - Parrot VM
http://en.wikibooks.org/wiki/Parrot_Virtual_Machine - Parrot Assembly Language
http://www.perl6.org/archive/pdd/pdd06_pasm.html - Parrot Reference: Chapter 11 – Perl 6 and Parrot Essentials
http://oreilly.com/perl/excerpts/perl-6-and-parrot-essentials/parrot-reference.html - Python Bytecode: Fun With Dis
http://akaptur.github.io/blog/2013/08/14/python-bytecode-fun-with-dis/ - Python's Innards: Hello, ceval.c!
http://tech.blog.aknin.name/category/my-projects/pythons-innards/ - Byterun
https://github.com/nedbat/byterun - Python Byte Code Instructions
http://document.ihg.uni-duisburg.de/Documentation/Python/lib/node56.html - Python Byte Code Instructions
https://docs.python.org/3.2/library/dis.html#python-bytecode-instructions - Lua 5.2 sources
http://www.lua.org/source/5.2/ - Lua 5.2 sources – lopcodes.h
http://www.lua.org/source/5.2/lopcodes.h.html - Lua 5.2 sources – lopcodes.c
http://www.lua.org/source/5.2/lopcodes.c.html - dis – Python module
https://docs.python.org/2/library/dis.html - Comparison of Python virtual machines
http://polishlinux.org/apps/cli/comparison-of-python-virtual-machines/ - O-code
http://en.wikipedia.org/wiki/O-code_machine - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html