Pohled pod kapotu JVM – volání funkcí a metod v JVM, Lua VM a Python VM

5. 8. 2014
Doba čtení: 23 minut

Sdílet

Dalšími důležitými instrukcemi, které jsou součástí bajtkódů prakticky všech virtuálních strojů, jsou instrukce určené pro volání funkcí, popř. metod. Právě touto poměrně rozsáhlou problematikou se budeme zabývat v dnešním článku. Nejdříve opět začneme s popisem JVM, později navážeme popisem Lua VM a Python VM.

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)

3. Instrukce invokestatic

4. Demonstrační příklad Test27.java: použití instrukce invokestatic

5. Instrukce invokevirtual

6. Demonstrační příklad Test28.java: použití instrukce invokevirtual

7. Instrukce invokespecial

8. Demonstrační příklad Test29.java: použití instrukce invokespecial

9. Instrukce invokeinterface

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ů

12. Odkazy na Internetu

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, invokespecialinvokeinterface. 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 highbytelowbyte 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()staticMethod4(), které jsou následně volány z (nestatických) metod callStaticMethod1()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()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 MethodRefconstant 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()nonStaticMethod4(), které jsou následně volány z testovacích metod callnonStaticMethod1()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()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í invokestaticinvokevirtual.

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 nonStaticPrivateMethod1nonStaticPrivateMethod4 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í duppop 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, invokevirtualinvokespecial, 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 highbytelowbyte 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()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()append() vypadá následovně:

bitcoin školení listopad 24

  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.or­g/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

  1. Python Byte Code Instructions
    https://docs.python.org/re­lease/2.5.2/lib/bytecodes­.html
  2. For-each Loop in Java
    http://www.leepoint.net/notes-java/flow/loops/foreach.html
  3. Programming in Lua (first edition)
    http://www.lua.org/pil/contents.html
  4. Programming in Lua: Numeric for
    http://www.lua.org/pil/4.3.4.html
  5. Programming in Lua: break and return
    http://www.lua.org/pil/4.4.html
  6. Programming in Lua: Tables
    http://www.lua.org/pil/2.5.html
  7. Programming in Lua: Table Constructors
    http://www.lua.org/pil/3.6.html
  8. Programovací jazyk Lua
    http://palmknihy.cz/web/kni­ha/programovaci-jazyk-lua-12651.htm
  9. Lua: Tables Tutorial
    http://lua-users.org/wiki/TablesTutorial
  10. Python 2.x: funkce range()
    https://docs.python.org/2/li­brary/functions.html#range
  11. Python 2.x: typ iterátor
    https://docs.python.org/2/li­brary/stdtypes.html#itera­tor-types
  12. Lua: Control Structure Tutorial
    http://lua-users.org/wiki/ControlStruc­tureTutorial
  13. Lua Types Tutorial
    http://lua-users.org/wiki/LuaTypesTutorial
  14. Goto Statement in Lua
    http://lua-users.org/wiki/GotoStatement
  15. Python break, continue and pass Statements
    http://www.tutorialspoint­.com/python/python_loop_con­trol.htm
  16. For Loop (Wikipedia)
    http://en.wikipedia.org/wiki/For_loop
  17. Heinz Rutishauser
    http://en.wikipedia.org/wi­ki/Heinz_Rutishauser
  18. Parrot
    http://www.parrot.org/
  19. Parrot languages
    http://www.parrot.org/languages
  20. Parrot Primer
    http://docs.parrot.org/pa­rrot/latest/html/docs/intro­.pod.html
  21. Parrot Opcodes
    http://docs.parrot.org/pa­rrot/latest/html/ops.html
  22. Parrot VM
    http://en.wikibooks.org/wi­ki/Parrot_Virtual_Machine
  23. Parrot Assembly Language
    http://www.perl6.org/archi­ve/pdd/pdd06_pasm.html
  24. Parrot Reference: Chapter 11 – Perl 6 and Parrot Essentials
    http://oreilly.com/perl/excerpts/perl-6-and-parrot-essentials/parrot-reference.html
  25. Python Bytecode: Fun With Dis
    http://akaptur.github.io/blog/2013/08/14/pyt­hon-bytecode-fun-with-dis/
  26. Python's Innards: Hello, ceval.c!
    http://tech.blog.aknin.na­me/category/my-projects/pythons-innards/
  27. Byterun
    https://github.com/nedbat/byterun
  28. Python Byte Code Instructions
    http://document.ihg.uni-duisburg.de/Documentation/Pyt­hon/lib/node56.html
  29. Python Byte Code Instructions
    https://docs.python.org/3­.2/library/dis.html#python-bytecode-instructions
  30. Lua 5.2 sources
    http://www.lua.org/source/5.2/
  31. Lua 5.2 sources – lopcodes.h
    http://www.lua.org/source/5­.2/lopcodes.h.html
  32. Lua 5.2 sources – lopcodes.c
    http://www.lua.org/source/5­.2/lopcodes.c.html
  33. dis – Python module
    https://docs.python.org/2/li­brary/dis.html
  34. Comparison of Python virtual machines
    http://polishlinux.org/ap­ps/cli/comparison-of-python-virtual-machines/
  35. O-code
    http://en.wikipedia.org/wiki/O-code_machine
  36. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  37. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  38. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  39. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/sa­fepoints-in-hotspot-jvm.html
  40. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/develo­perworks/java/library/j-jtp10185/
  41. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hot­spot/hotspot/file/tip/src/sha­re/tools/hsdis/README
  42. Java SE 6 Performance White Paper
    http://www.oracle.com/technet­work/java/6-performance-137236.html
  43. Lukas Stadler's Blog
    http://classparser.blogspot­.cz/2010/03/hsdis-i386dll.html
  44. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  45. PrintAssembly
    https://wikis.oracle.com/dis­play/HotSpotInternals/Prin­tAssembly
  46. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/ja­vase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  47. The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
  48. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.4
  49. The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.7
  50. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  51. ASM Home page
    http://asm.ow2.org/
  52. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  53. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  54. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  55. BCEL Home page
    http://commons.apache.org/bcel/
  56. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  57. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  58. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  59. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  60. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  61. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  62. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  63. Javassist
    http://www.jboss.org/javassist/
  64. Byteman
    http://www.jboss.org/byteman
  65. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  66. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  67. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  68. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  69. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  70. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  71. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  72. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  73. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  74. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  75. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  76. Cobertura
    http://cobertura.sourceforge.net/
  77. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.