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
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/int/float | načte konstantu z constant poolu (může se jednat i o referenci) | |
19 | ldc_w | 0×13 | hi-byte | lowbyte | string/ref/int/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+parametrů 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:
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
- Java quick guide: JVM Instruction Set
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - Root.cz: Využití komprimovaných ukazatelů na objekty v JVM
http://www.root.cz/clanky/vyuziti-komprimovanych-ukazatelu-na-objekty-v-nbsp-jvm/ - Root.cz: JamVM aneb alternativa k HotSpotu nejenom pro embedded zařízení a chytré telefony
http://www.root.cz/clanky/jamvm-aneb-alternativa-k-hotspotu-nejenom-pro-embedded-zarizeni-tablety-a-chytre-telefony/ - 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 - BCEL Home page
http://commons.apache.org/bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - 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/ - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - 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/ - FindBugs
http://findbugs.sourceforge.net/ - GNU Classpath
www.gnu.org/s/classpath/ - Java VMs Compared
http://bugblogger.com/java-vms-compared-160/ - JSRs: Java Specification Requests – JSR 223: Scripting for the Java Platform
http://www.jcp.org/en/jsr/detail?id=223 - Scripting for the Java Platform
http://java.sun.com/developer/technicalArticles/J2SE/Desktop/scripting/ - Scripting for the Java Platform (Wikipedia)
http://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform - Java Community Process
http://en.wikipedia.org/wiki/Java_Specification_Request - Java HotSpot VM Options
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html - Great Computer Language Shootout
http://c2.com/cgi/wiki?GreatComputerLanguageShootout - Java performance
http://en.wikipedia.org/wiki/Java_performance - Trying the prototype
http://mail.openjdk.java.net/pipermail/lambda-dev/2010-August/002179.html - Better closures (for Java)
http://blogs.sun.com/jrose/entry/better_closures - Lambdas in Java: An In-Depth Analysis
http://www.infoq.com/articles/lambdas-java-analysis - Class ReflectiveOperationException
http://download.java.net/jdk7/docs/api/java/lang/ReflectiveOperationException.html - Proposal: Indexing access syntax for Lists and Maps
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/001108.html - Proposal: Elvis and Other Null-Safe Operators
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/000047.html - Java 7 : Oracle pushes a first version of closures
http://www.baptiste-wicht.com/2010/05/oracle-pushes-a-first-version-of-closures/ - Groovy: An agile dynamic language for the Java Platform
http://groovy.codehaus.org/Operators - Better Strategies for Null Handling in Java
http://www.slideshare.net/Stephan.Schmidt/better-strategies-for-null-handling-in-java - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - Java Virtual Machine
http://en.wikipedia.org/wiki/Java_virtual_machine - ==, .equals(), compareTo(), and compare()
http://leepoint.net/notes-java/data/expressions/22compareobjects.html - New JDK7 features
http://openjdk.java.net/projects/jdk7/features/ - Project Coin: Bringing it to a Close(able)
http://blogs.sun.com/darcy/entry/project_coin_bring_close - CloseableFinder source code
http://blogs.sun.com/darcy/resource/ProjectCoin/CloseableFinder.java - Joe Darcy blog about JDK
http://blogs.sun.com/darcy - Java 7 – more dynamics
http://www.baptiste-wicht.com/2010/04/java-7-more-dynamics/ - New JDK 7 Feature: Support for Dynamically Typed Languages in the Java Virtual Machine
http://java.sun.com/developer/technicalArticles/DynTypeLang/index.html