Hlavní navigace

Pohled pod kapotu JVM (6.část - instrukční soubor virtuálního stroje Javy)

17. 1. 2012
Doba čtení: 26 minut

Sdílet

V dnešní části seriálu o programovacím jazyce Java si popíšeme první část instrukcí tvořících instrukční soubor virtuálního stroje Javy. Jedná se o instrukce sloužící pro uložení konstant na zásobník operandů, instrukce pro přesuny dat mezi proměnnými a zásobníkem operandů a taktéž o konverzní instrukce.

Obsah

1. Pohled pod kapotu JVM (6.část – instrukční soubor virtuálního stroje Javy)

2. Instrukce sloužící pro uložení konstanty zvoleného typu na zásobník operandů

3. Demonstrační příklad: využití instrukcí pro uložení konstanty na zásobník

4. Instrukce sloužící pro přesuny dat mezi lokálními proměnnými a zásobníkem operandů

5. Demonstrační příklad: přesun dat mezi parametry metody a zásobníkem

6. Instrukce pro přímou manipulaci s prvky uloženými na zásobníku operandů

7. Instrukce pro konverzi mezi různými datovými typy

8. Demonstrační příklad: konverze mezi různými datovými typy

9. Odkazy na Internetu

1. Pohled pod kapotu JVM (6.část – instrukční soubor virtuálního stroje Javy)

V dnešní části seriálu o programovacím jazyku Java i o vlastnostech JVM (Java Virtual Machine) budeme pokračovat v popisu instrukčního souboru zpracovávaného buď přímo virtuálním strojem Javy (interpretace bajtkódu), nebo překládaného s využitím JIT (Just In Time) překladače do nativního strojového kódu konkrétního mikroprocesoru, na němž virtuální stroj Javy běží. Připomeňme si, že instrukce použité v instrukční sadě JVM pracují s operandy uloženými na takzvaném zásobníku operandů, který současně slouží i pro předávání parametrů volaným metodám a následně pro získání návratové hodnoty těchto metod. Některé instrukce taktéž pracují s operandy uloženými v zásobníkovém rámci (stack frame), v němž se nachází jak parametry předané volané metodě, tak i oblast vyhrazená pro lokální proměnné této metody. Velikost této paměťové oblasti, na níž se můžeme dívat jako na pole s možností indexace jednotlivých prvků, je zjištěna již při překladu zdrojových kódů a virtuální stroj Javy automaticky provádí kontrolu, zda nedochází k překročení indexu, tj. ke čtení či zápisu mimo vyhrazenou oblast (jedná se ovšem o pole nehomogenní, protože jeho prvky mohou mít různý typ).

Logická struktura paměti spravované virtuálním strojem Javy, která nás nyní bude zajímat, je zobrazena pod tímto odstavcem:

+============================================+
|              Zásobník (stack)              |
+============================================+
|                                            |
|   +------------------------------------+   |
|   | Zásobníkový rámec #1 (stack frame) |   |
|   +------------------------------------+   |
|   |                                    |   |
|   |      +---------------------+       |   |
|   |      |  Zásobník operandů  |       |   |
|   |      +---------------------+       |   |
|   |                                    |   |
|   |      +---------------------+       |   |
|   |      |  Parametry metody   |       |   |
|   |      |.....................|       |   |
|   |      |  Lokální proměnné   |       |   |
|   |      +---------------------+       |   |
|   |                                    |   |
|   +------------------------------------+   |
|                                            |
|   +------------------------------------+   |
|   | Zásobníkový rámec #2 (stack frame) |   |
|   +------------------------------------+   |
|   |                                    |   |
|   |      +---------------------+       |   |
|   |      |  Zásobník operandů  |       |   |
|   |      +---------------------+       |   |
|   |                                    |   |
|   |      +---------------------+       |   |
|   |      |  Parametry metody   |       |   |
|   |      |.....................|       |   |
|   |      |  Lokální proměnné   |       |   |
|   |      +---------------------+       |   |
|   |                                    |   |
|   +------------------------------------+   |
|                                            |
|   +------------------------------------+   |
|   | Zásobníkový rámec #3 (stack frame) |   |
|   +------------------------------------+   |
|   |                                    |   |
|   |      +---------------------+       |   |
|   |      |  Zásobník operandů  |       |   |
|   |      +---------------------+       |   |
|   |                                    |   |
|   |      +---------------------+       |   |
|   |      |  Parametry metody   |       |   |
|   |      |.....................|       |   |
|   |      |  Lokální proměnné   |       |   |
|   |      +---------------------+       |   |
|   |                                    |   |
|   +------------------------------------+   |
|                                            |
|  ::::::::::::::::::::::::::::::::::::::::  |
|  :: Další zásobníkové rámce vytvářené  ::  |
|  :: v čase běhu aplikace při volání    ::  |
|  :: metod.                             ::  |
|  ::::::::::::::::::::::::::::::::::::::::  |
+============================================+

2. Instrukce sloužící pro uložení konstanty zvoleného typu na zásobník operandů

První skupinou instrukcí, o nichž se dnes zmíníme, jsou instrukce sloužící pro uložení konstanty na zásobník operandů. Ovšem ještě před uvedením seznamu jednotlivých instrukcí si musíme říci dva důležité fakty o celém instrukčním souboru JVM. Prvním faktem je, že všechny dále popsané instrukce mají operační kód o délce pouhého jednoho bajtu, přičemž se za operačním kódem u některých instrukcí nachází jeden či více dalších bajtů představujících buď přímo celočíselnou konstantu nebo index do constant poolu. Druhým faktem, který možná „assemblerovským puristům“ (mezi které jsem také ještě nedávno patřil :-) může vadit, je to, že instrukční sada JVM není v žádném případě ortogonální, tj. mnohé instrukce pracují pouze s omezeným počtem datových typů a pro ostatní datové typy neexistuje ekvivalentní instrukce. To například znamená, že existuje jen velmi málo instrukcí pro operace nad datovými typy byte, short a char a prakticky žádná instrukce pro datový typ boolean.

Naproti tomu však některé další instrukce obsahují přímo ve svém operačním kódu (tj. v onom jednom bajtu) krátkou celočíselnou konstantu, která se využívá buď jako index do oblasti lokálních proměnných nebo jako skutečná číselná konstanta. Tato technika je použita z toho důvodu, aby byl bajtkód co možná nejkratší, proto se také nejvíce místa v instrukční sadě „obětovalo“ na instrukce sloužící pro uložení konstanty typu int. V následující tabulce jsou vypsány všechny instrukce sloužící pro uložení číselné konstanty na zásobník. Ve sloupci „Instrukce“ se nachází symbolické jméno instrukce, sloupec „Opkód“ obsahuje bajtovou hodnotu uloženou přímo v bajtkódu (tedy operační kód instrukce), ve sloupcích „Data 1“ a „Data 2“ jsou vypsány bajty, které jsou případně uloženy ihned za operačním kódem a ve sloupci „Typ“ je uveden datový typ skutečně uložený na zásobník operandů:

# Instrukce Opkód Data 1 Data 2 Typ na zásobníku Popis
01 aconst_null 0×01     ref. uložení reference „null“ na zásobník
02 iconst_m1 0×02     int uložení konstanty –1 na zásobník
03 iconst0 0×03     int uložení konstanty 0 na zásobník
04 iconst1 0×04     int uložení konstanty 1 na zásobník
05 iconst2 0×05     int uložení konstanty 2 na zásobník
06 iconst3 0×06     int uložení konstanty 3 na zásobník
07 iconst4 0×07     int uložení konstanty 4 na zásobník
08 iconst5 0×08     int uložení konstanty 5 na zásobník
09 lconst0 0×09     long uložení konstanty 0L na zásobník
10 lconst1 0×0a     long uložení konstanty 1L na zásobník
11 fconst0 0×0b     float uložení konstanty 0.0f na zásobník
12 fconst1 0×0c     float uložení konstanty 1.0f na zásobník
13 fconst2 0×0d     float uložení konstanty 2.0f na zásobník
14 dconst0 0×0e     double uložení konstanty 0.0 na zásobník
15 dconst1 0×0f     double uložení konstanty 1.0 na zásobník
16 bipush 0×10 byteconst   int uložení „byteconst“ na zásobník s konverzí na int
17 sipush 0×11 hi-byte lowbyte int uložení slova hibyte-lowbyte na zásobník s konverzí na int
18 ldc 0×12 index   string/ref/in­t/float načte konstantu z constant poolu (může se jednat i o referenci)
19 ldc_w 0×13 hi-byte lowbyte string/ref/in­t/float načte konstantu z constant poolu (index je šestnáctibitový)
20 ldc2_w 0×14 hi-byte lowbyte long/double totéž co předchozí instrukce, ale pro typy long a double (ty mají v constant poolu vyhrazeny dvě položky)

3. Demonstrační příklad: využití instrukcí pro uložení konstanty na zásobník

Při pohledu na tabulku uvedenou v předchozí kapitole se může zdát, že některé instrukce jsou zdánlivě shodné. Například se jedná o pár instrukcí iconst1 a lconst1. Ve skutečnosti však tyto instrukce pracují s rozdílnými datovými typy, což je kontrolováno za běhu JVM. Totéž platí i u trojice instrukcí ldc* u nichž je typ zjištěn přímo z typu konstanty uložené na constant poolu (typicky se jedná o reference na objekty či o reálné konstanty). Všimněte si také, že práce s typy long a double téměř vždy vede k vygenerování delšího bajtkódu, protože konstanty tohoto typu uložené na constant poolu lze adresovat jen s využitím šestnáctibitového indexu (popravdě řečeno je v tomto případě jak struktura constant poolu, tak i struktura bajtkódu navržena dosti nešťastně a nelogicky; jen těžko lze odhadnout, kolik kilobajtů paměťového prostoru je kvůli této nelogičnosti zbytečně alokováno pro každou běžící JVM).

Pojďme se podívat ještě na další vlastnosti instrukcí vypsaných v předchozí tabulce. Zajímavé například je, že některé příkazy lze provést více způsoby, protože například instrukce iconst1, bipush 1 a sipush 1 jsou funkčně ekvivalentní – všechny totiž slouží pro uložení konstanty 1 typu int na zásobník operandů. Ve skutečnosti ale překladač v tomto případě vždy vybere nejkratší instrukci, tj. iconst1 (ta má délku jen jeden bajt, zatímco bipush 1 má dva bajty a sipush 1 dokonce bajty tři). Totéž platí například pro instrukce bipush 100, sipush 100 a ldc (index na konstantu 100 uloženou na constant poolu). I v tomto případě překladač vybere první instrukci, protože ne nejkratší (už jen z toho důvodu, že se ušetří jeden záznam na constant poolu). Vše si ukážeme na následujícím demonstračním příkladu, jehož zdrojový kód má tvar:

class Test1 {
 
    static void method1() {
        nop(0, 1, 2);
    }
 
    static void method2() {
        nop(3, 4, 5);
    }
 
    static void method3() {
        nop(6, 7, 8);
    }
 
    static void method4() {
        nop(32, 127, -128);
    }
 
    static void method5() {
        nop(128, 32767, -32768);
    }
 
    static void method6() {
        nop(0x12345678, 0x00aabbcc, 789789);
    }
 
    static void method7() {
        nop(0.0f, 1.0f);
    }
 
    static void method8() {
        nop(2.0f, 3.0f);
    }
 
    static void method9() {
        nop(0.0, 1.0);
    }
 
    static void method10() {
        nop(2.0, 3.0);
    }
 
    static void method11() {
        nop(null);
    }
 
    static void method12() {
        nop("pokus");
    }
 
    static void nop(int a, int b, int c) {
        // nic ;-)
    }
 
    static void nop(float a, float b) {
        // opět nic ;-)
    }
 
    static void nop(double a, double b) {
        // opět nic ;-)
    }
 
    static void nop(Object o) {
        // opět nic ;-)
    }
 
}

Jak je ze zdrojového kódu třídy Test1 patrné, je v něm z několika testovacích metod volána přetížená statická metoda nop(int, int, int), nop(float, float), nop(double, double) či nop(Object), která ve skutečnosti nic neprovádí a taktéž nic nevrací (její návratový typ je ve všech případech void). My budeme sledovat, jakým způsobem se přeloží zbylé metody method*, které musí před zavoláním nop() umístit všechny parametry na zásobník, tj. použít instrukce z tabulky uvedené ve druhé kapitole. Následuje výpis disassemlovaného bajtkódu s ručně dopsanými poznámkami:

static void method1();
  Code:
   Stack=3, Locals=0, Args_size=0
   0:   iconst_0            // konstanta 1 je přímo součástí operačního kódu instrukce
   1:   iconst_1            // konstanta 2 je přímo součástí operačního kódu instrukce
   2:   iconst_2            // konstanta 3 je přímo součástí operačního kódu instrukce
   3:   invokestatic    #2; // volání metody void nop(int, int, int)
   6:   return              // návrat do volající metody
static void method2();
  Code:
   Stack=3, Locals=0, Args_size=0
   0:   iconst_3            // konstanta 4 je přímo součástí operačního kódu instrukce
   1:   iconst_4            // konstanta 5 je přímo součástí operačního kódu instrukce
   2:   iconst_5            // konstanta 6 je přímo součástí operačního kódu instrukce
   3:   invokestatic    #2; // volání metody void nop(int, int, int)
   6:   return              // návrat do volající metody
static void method3();
  Code:
   Stack=3, Locals=0, Args_size=0
   0:   bipush  6           // konstanta o rozsahu (signed) byte uložená ihned za operačním kódem
   2:   bipush  7           // konstanta o rozsahu (signed) byte uložená ihned za operačním kódem
   4:   bipush  8           // konstanta o rozsahu (signed) byte uložená ihned za operačním kódem
   6:   invokestatic    #2; // volání metody void nop(int, int, int)
   9:   return              // návrat do volající metody
static void method4();
  Code:
   Stack=3, Locals=0, Args_size=0
   0:   bipush  32          // konstanta o rozsahu (signed) byte uložená ihned za operačním kódem
   2:   bipush  127         // konstanta o rozsahu (signed) byte uložená ihned za operačním kódem
   4:   bipush  -128        // konstanta o rozsahu (signed) byte uložená ihned za operačním kódem
   6:   invokestatic    #2; // volání metody void nop(int, int, int)
   9:   return              // návrat do volající metody
static void method5();
  Code:
   Stack=3, Locals=0, Args_size=0
   0:   sipush  128         // konstanta o rozsahu (signed) short uložená ihned za operačním kódem
   3:   sipush  32767       // konstanta o rozsahu (signed) short uložená ihned za operačním kódem
   6:   sipush  -32768      // konstanta o rozsahu (signed) short uložená ihned za operačním kódem
   9:   invokestatic    #2; // volání metody void nop(int, int, int)
   12:  return              // návrat do volající metody
static void method6();
  Code:
   Stack=3, Locals=0, Args_size=0
   0:   ldc #3;             // celočíselná konstanta typu int 305419896 načtená z constant poolu
   2:   ldc #4;             // celočíselná konstanta typu int 11189196 načtená z constant poolu
   4:   ldc #5;             // celočíselná konstanta typu int 789789 načtená z constant poolu
   6:   invokestatic    #2; // volání metody void nop(int, int, int)
   9:   return              // návrat do volající metody
static void method7();
  Code:
   Stack=2, Locals=0, Args_size=0
   0:   fconst_0            // konstanta 0.0f je přímo součástí operačního kódu instrukce
   1:   fconst_1            // konstanta 1.0f je přímo součástí operačního kódu instrukce
   2:   invokestatic    #6; // volání metody void nop(float, float)
   5:   return              // návrat do volající metody
static void method8();
  Code:
   Stack=2, Locals=0, Args_size=0
   0:   fconst_2            // konstanta 2.0f je přímo součástí operačního kódu instrukce
   1:   ldc #7;             // konstanta typu float 3.0f načtená z constant poolu
   3:   invokestatic    #6; // volání metody void nop(float, float)
   6:   return              // návrat do volající metody
static void method9();
  Code:
   Stack=4, Locals=0, Args_size=0
   0:   dconst_0            // konstanta 0.0 je přímo součástí operačního kódu instrukce
   1:   dconst_1            // konstanta 1.0 je přímo součástí operačního kódu instrukce
   2:   invokestatic    #8; // volání metody void nop(double, double)
   5:   return
static void method10();
  Code:
   Stack=4, Locals=0, Args_size=0
   0:   ldc2_w  #9;         // konstanta typu double 2.0d načtená z constant poolu
   3:   ldc2_w  #11;        // konstanta typu double 3.0d načtená z constant poolu
   6:   invokestatic    #8; // volání metody void nop(double, double)
   9:   return
static void method11();
  Code:
   Stack=1, Locals=0, Args_size=0
   0:   aconst_null         // konstanta null je součástí operačního kódu instrukce
   1:   invokestatic   #13; // volání metody void nop(Object)
   4:   return
static void method12();
  Code:
   Stack=1, Locals=0, Args_size=0
   0:   ldc #14;            // String "pokus" je uložen v constant poolu
   2:   invokestatic   #13; // volání metody void nop(Object)
   5:   return

4. Instrukce sloužící pro přesuny dat mezi lokálními proměnnými a zásobníkem operandů

Další poměrně velkou skupinu instrukcí tvoří instrukce sloužící pro přesuny dat mezi lokálními proměnnými a případnými parametry metody na jedné straně a zásobníkem operandů na straně druhé. Je pravděpodobně zřejmé, že způsob adresace dat bude odlišný na straně lokálních proměnných i na straně zásobníku operandů. V případě lokálních proměnných+pa­rametrů je totiž každý prvek určen svým indexem, který je nedílnou součástí instrukce (jak jsme si již řekli minule a ostatně i v první kapitole, lze na tuto oblast zásobníkového rámce pohlížet jako na pole, jehož prvky lze snadno indexovat). U zásobníku operandů se vždy pracuje s prvkem umístěným na vrcholu zásobníku (TOS=Top Of Stack); ostatní prvky umístěné pod TOS jsou nedostupné.

Všechny instrukce tohoto typu se jmenují *load# popř. *store#, přičemž za hvězdičku se doplňuje první písmeno datového typu a za křížek se v některých případech udává index do oblasti lokálních proměnných – to ovšem jen v případě těch instrukcí, u nichž je index součástí instrukčního slova. V opačném případě je index uložen ve formě jednoho bajtu ihned za instrukčním kódem. Pokud je nutné adresovat prvek s indexem větším než 255 (typicky u začátečnických programů v velkým množstvím lokálních proměnných v jediné metodě :-)), lze před instrukci typu load/store vložit prefix wide – poté lze použít šestnáctibitový index namísto indexu osmibitového. Tuto zajímavou instrukci (či spíše instrukční prefix) si podrobněji popíšeme příště:

# Instrukce Opkód Data1 Typ dat Popis
01 iload 15 index int načtení lokální proměnné typu int z pozice „index“ a její uložení na TOS
02 lload 16 index long načtení lokální proměnné typu long z pozice „index“ a její uložení na TOS
03 fload 17 index float načtení lokální proměnné typu float z pozice „index“ a její uložení na TOS
04 dload 18 index double načtení lokální proměnné typu double z pozice „index“ a její uložení na TOS
05 aload 19 index reference načtení lokální proměnné typu reference na objekt z pozice „index“ a její uložení na TOS
06 iload0 1A   int načtení lokální proměnné typu int z pozice číslo 0 a její uložení na TOS
07 iload1 1B   int načtení lokální proměnné typu int z pozice číslo 1 a její uložení na TOS
08 iload2 1C   int načtení lokální proměnné typu int z pozice číslo 2 a její uložení na TOS
09 iload3 1D   int načtení lokální proměnné typu int z pozice číslo 3 a její uložení na TOS
10 lload0 1E   long načtení lokální proměnné typu long z pozice číslo 0 a její uložení na TOS
11 lload1 1F   long načtení lokální proměnné typu long z pozice číslo 1 a její uložení na TOS
12 lload2 20   long načtení lokální proměnné typu long z pozice číslo 2 a její uložení na TOS
13 lload3 21   long načtení lokální proměnné typu long z pozice číslo 3 a její uložení na TOS
14 fload0 22   float načtení lokální proměnné typu float z pozice číslo 0 a její uložení na TOS
15 fload1 23   float načtení lokální proměnné typu float z pozice číslo 1 a její uložení na TOS
16 fload2 24   float načtení lokální proměnné typu float z pozice číslo 2 a její uložení na TOS
17 fload3 25   float načtení lokální proměnné typu float z pozice číslo 3 a její uložení na TOS
18 dload0 26   double načtení lokální proměnné typu double z pozice číslo 0 a její uložení na TOS
19 dload1 27   double načtení lokální proměnné typu double z pozice číslo 1 a její uložení na TOS
20 dload2 28   double načtení lokální proměnné typu double z pozice číslo 2 a její uložení na TOS
21 dload3 29   double načtení lokální proměnné typu double z pozice číslo 3 a její uložení na TOS
22 aload0 2A   reference načtení lokální proměnné typu reference na objekt z pozice číslo 0 a její uložení na TOS
23 aload1 2B   reference načtení lokální proměnné typu reference na objekt z pozice číslo 1 a její uložení na TOS
24 aload2 2C   reference načtení lokální proměnné typu reference na objekt z pozice číslo 2 a její uložení na TOS
25 aload3 2D   reference načtení lokální proměnné typu reference na objekt z pozice číslo 3 a její uložení na TOS
26 istore 36 index int přesun hodnoty z TOS do lokální proměnné umístěné na pozici „index“ (kontrola na typ int)
27 lstore 37 index long přesun hodnoty z TOS do lokální proměnné umístěné na pozici „index“ (kontrola na typ long)
28 fstore 38 index float přesun hodnoty z TOS do lokální proměnné umístěné na pozici „index“ (kontrola na typ float)
29 dstore 39 index double přesun hodnoty z TOS do lokální proměnné umístěné na pozici „index“ (kontrola na typ double)
30 astore 3A index reference přesun hodnoty z TOS do lokální proměnné umístěné na pozici „index“ (kontrola, zda jde o objekt)
31 istore0 3B   int přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 0 (kontrola na typ int)
32 istore1 3C   int přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 1 (kontrola na typ int)
33 istore2 3D   int přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 2 (kontrola na typ int)
34 istore3 3E   int přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 3 (kontrola na typ int)
35 lstore0 3F   long přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 0 (kontrola na typ long)
36 lstore1 40   long přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 1 (kontrola na typ long)
37 lstore2 41   long přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 2 (kontrola na typ long)
38 lstore3 42   long přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 3 (kontrola na typ long)
39 fstore0 43   float přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 0 (kontrola na typ float)
40 fstore1 44   float přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 1 (kontrola na typ float)
41 fstore2 45   float přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 2 (kontrola na typ float)
42 fstore3 46   float přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 3 (kontrola na typ float)
43 dstore0 47   double přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 0 (kontrola na typ double)
44 dstore1 48   double přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 1 (kontrola na typ double)
45 dstore2 49   double přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 2 (kontrola na typ double)
46 dstore3 4A   double přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 3 (kontrola na typ double)
47 astore0 4B   reference přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 0 (kontrola na typ reference na objekt)
48 astore1 4C   reference přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 1 (kontrola na typ reference na objekt)
49 astore2 4D   reference přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 2 (kontrola na typ reference na objekt)
50 astore3 4E   reference přesun hodnoty z TOS do lokální proměnné umístěné na pozici číslo 3 (kontrola na typ reference na objekt)

5. Demonstrační příklad: přesun dat mezi parametry metody a zásobníkem

Podobně jako u předchozí skupiny instrukcí, i u instrukcí typu load a store je možné vidět snahu tvůrců specifikace JVM o optimalizaci bajtkódu na jeho celkovou velikost – týká se to především přístupu k prvním parametrům metody a/nebo lokálním proměnným s využitím konstantního indexu uloženého přímo v operačním kódu instrukce. I z tohoto důvodu je vhodné u všech metod nepracujících s atributy objektu používat modifikátor static, čímž se ušetří jedna pozice v oblasti lokálních proměnných, která by jinak byla rezervována pro předání hodnoty this. Podívejme se však opět na demonstrační příklad, který nám ukáže, jakým způsobem překladač generuje instrukce typu load a store v reálném bajtkódu. Zdrojový kód demonstračního příkladu je opět poměrně jednoduchý – jedná se o třídu obsahující metody, které pracují pouze se svými parametry (to abychom se vyhnuli práci s konstantami, protože to již známe z předchozích kapitol). Aby byl vidět rozdíl mezi přístupem k prvním čtyřem parametrům a zbylými parametry, má každá metoda navíc i dvojici „výplňových“ parametrů:

class Test2 {
 
    static void method1(int a, int b, boolean vypln1, boolean vypln2, int c) {
        a = b;
        b = c;
        c = a;
    }
 
    static void method2(long a, long b, boolean vypln1, boolean vypln2, long c) {
        a = b;
        b = c;
        c = a;
    }
 
    static void method3(float a, float b, boolean vypln1, boolean vypln2, float c) {
        a = b;
        b = c;
        c = a;
    }
 
    static void method4(double a, double b, boolean vypln1, boolean vypln2, double c) {
        a = b;
        b = c;
        c = a;
    }
 
    static void method5(String a, String b, boolean vypln1, boolean vypln2, String c) {
        a = b;
        b = c;
        c = a;
    }
 
}

Disassemlovaný bajtkód všech pěti testovacích metod s ručně dopsanými komentáři vypadá následovně:

static void method1(int, int, boolean, boolean, int);
  Code:
   Stack=1, Locals=5, Args_size=5
   0:   iload_1           // index je součástí operačního kódu instrukce
   1:   istore_0          // index je součástí operačního kódu instrukce
   2:   iload   4         // index je uveden v bajtu za operačním kódem
   4:   istore_1          // index je součástí operačního kódu instrukce
   5:   iload_0           // index je součástí operačního kódu instrukce
   6:   istore  4         // index je uveden v bajtu za operačním kódem
   8:   return            // návrat do volající metody
static void method2(long, long, boolean, boolean, long);
  Code:
   Stack=2, Locals=8, Args_size=5
   0:   lload_2           // index je součástí operačního kódu instrukce
   1:   lstore_0          // index je součástí operačního kódu instrukce
   2:   lload   6         // index je uveden v bajtu za operačním kódem
   4:   lstore_2          // index je součástí operačního kódu instrukce
   5:   lload_0           // index je součástí operačního kódu instrukce
   6:   lstore  6         // index je uveden v bajtu za operačním kódem
   8:   return            // návrat do volající metody
static void method3(float, float, boolean, boolean, float);
  Code:
   Stack=1, Locals=5, Args_size=5
   0:   fload_1           // index je součástí operačního kódu instrukce
   1:   fstore_0          // index je součástí operačního kódu instrukce
   2:   fload   4         // index je uveden v bajtu za operačním kódem
   4:   fstore_1          // index je součástí operačního kódu instrukce
   5:   fload_0           // index je součástí operačního kódu instrukce
   6:   fstore  4         // index je uveden v bajtu za operačním kódem
   8:   return            // návrat do volající metody
static void method4(double, double, boolean, boolean, double);
  Code:
   Stack=2, Locals=8, Args_size=5
   0:   dload_2           // index je součástí operačního kódu instrukce
   1:   dstore_0          // index je součástí operačního kódu instrukce
   2:   dload   6         // index je uveden v bajtu za operačním kódem
   4:   dstore_2          // index je součástí operačního kódu instrukce
   5:   dload_0           // index je součástí operačního kódu instrukce
   6:   dstore  6         // index je uveden v bajtu za operačním kódem
   8:   return            // návrat do volající metody
static void method5(java.lang.String, java.lang.String, boolean, boolean, java.lang.String);
  Code:
   Stack=1, Locals=5, Args_size=5
   0:   aload_1           // index je součástí operačního kódu instrukce
   1:   astore_0          // index je součástí operačního kódu instrukce
   2:   aload   4         // index je uveden v bajtu za operačním kódem
   4:   astore_1          // index je součástí operačního kódu instrukce
   5:   aload_0           // index je součástí operačního kódu instrukce
   6:   astore  4         // index je uveden v bajtu za operačním kódem
   8:   return            // návrat do volající metody

6. Instrukce pro přímou manipulaci s prvky uloženými na zásobníku operandů

Mohlo by se zdát, že pokud zásobník operandů slouží pouze k přípravě parametrů pro volanou metodu, popř. k jednoduchým výpočtům, nejsou potřeba žádné další instrukce, které by sloužily pro manipulaci s daty uloženými na zásobníku operandů. Ve skutečnosti to však není zcela pravda, protože se velmi často stane, že je zapotřebí odstranit nějaký prvek ze zásobníku a nikam ho přitom neukládat, popř. zduplikovat prvek, který se na zásobníku nachází. Pro tyto případy obsahuje instrukční sada virtuálního stroje Javy několik instrukcí, které se nápadně podobají slovům používaným v programovacím jazyku Forth (ono se ostatně není čemu divit, protože se jedná skutečně o základní instrukce). Důležité je, že tyto instrukce již nepotřebují být rozděleny podle toho s jakými daty pracují, protože každému prvku uloženém na zásobníku operandů je stejně přiřazen datový tag, který je těmito instrukcemi využíván. Zajímavé je, že podobný princip nebyl použit například u aritmetických instrukcí (bavil jsem se o tom s jedním vývojářem VM, kterému se to také příliš nelíbilo, ale už si nepamatoval, kdo vlastně s návrhem instrukční sady před více než patnácti lety u společnosti Sun přišel):

# Instrukce Opkód Popis
1 pop 57 odstraní jednu položku z TOS (platí pro int, float, referenci na objekt)
2 pop2 58 odstraní jednu (long, double) položku či dvě (int, float, reference) položky ze zásobníku
3 dup 59 zduplikuje (zkopíruje) položku z TOS (platí pro int, float, referenci na objekt)
4 dup_x1 5A zduplikuje (zkopíruje) položku z TOS, ale vloží i o dvě pozice níž do zásobníku
5 dup_x2 5B zduplikuje (zkopíruje) položku z TOS a vloží i o dvě či tři pozice níž (v závislosti na bitové šířce)
6 dup2 5C duplikace jedné (long, double) či dvou (int, float, reference) položek
7 dup2_x1 5D kombinace vlastností instrukcí dup2 a dup_x1 (vložení prvku o dvě místa níže)
8 dup2_x2 5E kombinace vlastností instrukcí dup2 a dup_x2
9 swap 5F prohodí dva prvky ležící na vrcholu zásobníku (platí pro int, float, referenci na objekt)

7. Instrukce pro konverzi mezi různými datovými typy

Čtvrtou skupinou instrukcí virtuálního stroje jazyka Java, s níž se dnes seznámíme, jsou instrukce sloužící pro konverzi dat. Programovací jazyk Java sice patří mezi jazyky silně typované, ale konverze mezi některými datovými typy jsou prováděny automaticky (byte-→int) a jiné lze explicitně zapsat. Navíc se konverzní instrukce objevují v bajtkódu například při vytváření návratové hodnoty metody v případě, že tato hodnota nemá typ int, long, float či double. Ovšem ne všechny kombinace konverzí datových typů jsou v instrukční sadě přítomny, takže některé konverze je ve skutečnosti nutné provádět pomocí dvojice instrukcí (například se může jednat o konverzi z float na short a podobně). Podívejme se ostatně na tabulku obsahující všechny konverzní instrukce. V prvním sloupci je uveden datový typ, z něhož je konverze prováděna a v prvním řádku naopak výsledný datový typ:

skoleni

z/na-> char byte short int long float double
char              
byte              
short              
int i2c i2b i2s   i2l i2f i2d
long       l2i   l2f l2d
float       f2i f2l   f2d
double       d2i d2l d2f  

8. Demonstrační příklad: konverze mezi různými datovými typy

Opět se podívejme na jednoduchý demonstrační příklad, v němž budou použity některé výše uvedené konverzní funkce. Příklad se skládá z několika metod – první čtyři metody provádí konverzi svého parametru na výstupní hodnotu a pátá metoda obsahuje několik lokálních proměnných inicializovaných proměnnými jiného typu. V některých případech je možné využít implicitní konverze, ovšem v mnoha dalších případech se nevyhneme explicitnímu přetypování:

class Test3 {
 
    static byte toByte(int i) {
        return (byte)i;
    }
 
    static short toShort(int i) {
        return (short)i;
    }
 
    static short toShort(float f) {
        return (short)f;
    }
 
    static short toShort(double d) {
        return (short)d;
    }
 
    static void vypocet(int x, int y) {
        long   a = x;
        float  f = y;
        float  g = a;
        int    i = (int)f;
        double d = i;
        byte   b = (byte)d;
    }
 
}

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

static byte toByte(int);
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   iload_0           // načtení prvního parametru metody
   1:   i2b               // převod na byte
   2:   ireturn           // návrat do volající metody s návratovou hodnotou na zásobníku
static short toShort(int);
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   iload_0           // načtení prvního parametru metody
   1:   i2s               // převod na short
   2:   ireturn           // návrat do volající metody s návratovou hodnotou na zásobníku
static short toShort(float);
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   fload_0           // načtení prvního parametru metody
   1:   f2i               // postupný převod z float
   2:   i2s               // ... na short přes typ int
   3:   ireturn           // návrat do volající metody s návratovou hodnotou na zásobníku
static short toShort(double);
  Code:
   Stack=2, Locals=2, Args_size=1
   0:   dload_0           // načtení prvního parametru metody
   1:   d2i               // postupný převod z double
   2:   i2s               // ... na short přes typ int
   3:   ireturn           // návrat do volající metody s návratovou hodnotou na zásobníku
static void vypocet(int, int);
  Code:
   Stack=2, Locals=10, Args_size=2
   0:   iload_0           //
   1:   i2l               // long a = x;
   2:   lstore_2          //
 
   3:   iload_1           //
   4:   i2f               // float f = y
   5:   fstore  4         //
 
   7:   lload_2           //
   8:   l2f               // float g = a
   9:   fstore  5         //
 
   11:  fload   4         //
   13:  f2i               // int i = (int)f;
   14:  istore  6         //
 
   16:  iload   6         //
   18:  i2d               // double d = i;
   19:  dstore  7         //
 
   21:  dload   7         //
   23:  d2i               // byte b = (byte)d;
   24:  i2b               // zde jde o postupný převod přes int
   25:  istore  9         //
 
   27:  return            // návrat do volající metody

9. Odkazy na Internetu

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