Hlavní navigace

Pohled pod kapotu JVM (2.část - podrobnější analýza obsahu constant poolu)

20. 12. 2011
Doba čtení: 23 minut

Sdílet

V devatenácté části seriálu o programovacím jazyce Java i o vlastnostech JVM budeme pokračovat v popisu bajtkódu, tj. interní struktury souborů s koncovkou .class. Dnes se zaměříme na strukturu položek umístěných v constant poolu. Taktéž bude ukázán poměrně jednoduchý nástroj, který dokáže obsah constant poolu vypsat.

Obsah

1. Malé zopakování z minula – struktura položek uložených v constant poolu

2. Nástroj pro zobrazení obsahu všech položek uložených v constant poolu

3. Formát constant poolu pro zcela prázdnou třídu odvozenou od třídy Object

4. 32bitové číselné konstanty použité v programovém kódu

5. 8bitové, 16bitové a 64bitové číselné konstanty

6. Způsob určení datových typů atributů a návratových hodnot metod

7. Demonstrační příklad – výpis návratových typů metod

8. Demonstrační příklad – výpis typů argumentů metod

9. Odkazy na Internetu

1. Malé zopakování z minula – struktura položek uložených v constant poolu

V dnešní části seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje tohoto jazyka se opět budeme zabývat popisem interní struktury souborů .class, tj. souborů obsahujících bajtkód jedné třídy, rozhraní nebo výčtu (enumeration). V předchozí části seriálu jsme si řekli, jak tyto soubory vlastně vznikají a jaká je jejich základní struktura. Taktéž jsme se zmínili o takzvaném constant poolu, jenž je nedílnou součástí bajtkódu. Právě této části bajtkódu se dnes budeme věnovat podrobněji. Připomeňme si, že v constant poolu se mohou nacházet položky několika typů. Typ každé položky je jednoznačně určen takzvaným tagem (jednobajtovou konstantou), za nímž následuje buď celočíselná či reálná konstanta, řetězec se zadanou délkou, popř. jeden či dva odkazy na další položku/položky umístěné v constant poolu.

Nejjednodušší strukturu mají položky, které přímo obsahují celočíselné či reálné konstanty. Jejich použití si podrobněji popíšeme ve čtvrté a taktéž v páté kapitole. Jedná se o položky s tagy 3, 4, 5 a 6:

Tag Název tagu Parametr 1 Parametr 2
3 Integer čtyřbajtová celočíselná konstanta ×
4 Float čtyřbajtová FP konstanta ×
5 Long osmibajtová celočíselná konstanta ×
6 Double osmibajtová FP konstanta ×

V každém constant poolu se taktéž nachází několik řetězců (majících různý význam), popř. i řetězcových konstant (literálů):

Tag Název tagu Parametr 1 Parametr 2
1 Utf8 délka řetězce v bajtech sekvence bajtů (počet znaků je obecně menší než počet bajtů)
8 String 2 bajty: odkaz na vlastní řetězec (záznam typu Utf8) ×

Dalších pět typů záznamů již neobsahuje žádné konstanty (ani číselné, ani řetězcové), ale odkazy na další položky umístěné v constant poolu. O způsobu práce s odkazy se podrobněji zmíníme ve třetí kapitole. Jedná se o položky s tagy 7, 9, 10, 11 a 12:

Tag Název tagu Parametr 1 Parametr 2
7 Class 2 bajty: odkaz na jméno třídy (záznam typu Utf8) ×
9 Fieldref 2 bajty: odkaz na jméno třídy (záznam typu Utf8) 2 bajty: odkaz na záznam typu NameAndType
10 Methodref 2 bajty: odkaz na jméno třídy (záznam typu Utf8) 2 bajty: odkaz na záznam typu NameAndType
11 InterfaceMethodref 2 bajty: odkaz na jméno třídy (záznam typu Utf8) 2 bajty: odkaz na záznam typu NameAndType
12 NameAndType 2 bajty: odkaz na záznam typu Utf8 2 bajty: odkaz na záznam typu Utf8

2. Nástroj pro zobrazení obsahu všech položek uložených v constant poolu

Javovský bajtkód i constant pool, jenž je jeho nedílnou součástí, je samozřejmě možné zkoumat mnoha různými způsoby. Pro vysokoúrovňovou manipulaci s bajtkódem je pravděpodobně nejlepší použít již minule zmíněné knihovny ASM a BCEL, k nimž se ještě v tomto seriálu vrátíme. Pro pouhý výpis obsahu constant poolu nicméně postačuje standardní utilita nazvaná javap, které je spuštěna s přepínačem -verbose. Avšak pro pochopení způsobu uložení informací v constant poolu je nejlepší si vytvořit vlastní program, který vhodným způsobem vypíše jeho obsah. Zdrojový kód tohoto programu (mimochodem, jedná se o výsledek jednoho letošního zimního večera :-) můžete získat na adrese http://i.iinfo­.cz/files/root/145/de­compiler-source.c, nicméně pro prohlížení jeho zdrojového kódu přímo v prohlížeči je lepší použít tento odkaz, pod nímž se skrývá zdrojový kód s obarvenou syntaxí.

Tento jednoduchý nástroj, který s velkou pravděpodobností obsahuje různé zákeřné chyby :-), je možné přeložit pomocí překladače odpovídajícího normě ANSI/ISO C, bude však pracovat korektně pouze na procesorech s pořadím bajtů little endian (na procesorech big endian se mohou špatně zpracovávat načtené číselné konstanty, ostatní části programu jsou však nezávislé na architektuře). Po překladu vznikne spustitelný soubor, který akceptuje jeden parametr – název souboru .class. Název se uvádí i s příponou, na rozdíl od utility javap (podle mého názoru je uvedení přípony souboru většinou praktičtější). Výsledek, tj. obsah constant poolu (plus několik dalších základních informací) je tisknut na standardní výstup. Princip práce tohoto programu je velmi jednoduchý – nejdříve je z bajtkódu získán obsah všech položek uložených v constant poolu, který je uložen do dynamicky alokovaného pole. Posléze je obsah tohoto pole vypsán, přičemž dochází k dereferencování některých informací (například odkaz na položky typu string je přímo nahrazen referencovaným řetězcem).

Důvod, proč jsou položky nejdříve z constant poolu načteny do dynamicky alokovaného pole, spočívá v tom, že reference mohou odkazovat na libovolné další položky. Typicky se totiž používají i dopředné reference, což znamená, že není možné, aby se obsah constant poolu ihned při načítání převáděl na standardní výstup. O načítání položek z bajtkódu se stará funkce process_constan­t_pool() společně s funkcí read_constant_po­ol_entry(). Výsledkem je dynamické pole s prvky typu PoolEntry (jde o strukturu obsahující jak tagy jednotlivých prvků, tak i vlastní data – číselné konstanty, řetězce či odkazy/reference). Všechny prvky uložené v tomto poli jsou posléze postupně vytištěny funkcí print_constan­t_pool_entry(), z níž se volají další pomocné funkce print_(typ_prvku­). Podrobnější informace o tom, proč je program vytvořen právě takovým způsobem, budou vysvětleny v dalších kapitolách (asi nejzáhadnější je návratová hodnota funkce read_constant_po­ol_entry() a způsob jejího použití při naplňování pole položkami získanými z constant poolu).

3. Formát constant poolu pro zcela prázdnou třídu odvozenou od třídy Object

Pojďme se nyní podívat na to, jak vypadá obsah constant poolu v reálných .class souborech, tj. v souborech vzniklých zkompilováním jediné třídy (či rozhraní, popř. výčtu). Začněme prakticky nejjednodušší třídou – prázdnou třídou odvozenou implicitně od třídy Object (jak jistě víte, každá třída, kterou programátor deklaruje, musí mít předka a pokud není předek explicitně uveden za klíčovým slovem extends, použije se jako implicitní předek právě třída Object). Na zdrojovém textu s definicí této třídy není nic záhadného ani složitého:

public class EmptyClass {
}

Nyní již můžeme snadno zjistit, jaké informace nám zobrazí výše popsaný nástroj. Poznamenejme, že překlad třídy EmptyClass byl proveden s přepínačem -g:none, aby se odstranily pro nás nepotřebné dodatkové informace (použití tohoto přepínače není nutné, ovšem nám zjednoduší analýzu obsahu constant poolu):

Magicka konstanta: cafebabe
Cislo verze:       50
Cislo podverze:    0

Velikost const. poolu: 9 prvku
  1   10  MethodRef         3   7    java/lang/Object.<init>()V
  2    7  Class             8        EmptyClass
  3    7  Class             9        java/lang/Object
  4    1  String                     "<init>"
  5    1  String                     "()V"
  6    1  String                     "Code"
  7   12  Name and type     4   5    <init>  ()V
  8    1  String                     "EmptyClass"
  9    1  String                     "java/lang/Object"

Zajímavé jistě je, že i pro tak jednoduchou a zdánlivě primitivní třídu obsahuje constant pool celých devět položek. Díky tomu, že se v pátém a šestém sloupci zobrazují reference na další položky poolu, lze snadno analyzovat druhou a třetí položku, která obsahuje jména tříd (přesněji řečeno odkazy na řetězce se jmény tříd) – konkrétně se jedná o plné názvy (názvy i s příslušným balíkem) naší prázdné třídy a jejího předka (Object):

  1   10  MethodRef         3   7          java/lang/Object.<init>()V
  2    7  Class             8---------.    EmptyClass
  3    7  Class             9------.  |    java/lang/Object
  4    1  String                   |  |    "<init>"
  5    1  String                   |  |    "()V"
  6    1  String                   |  |    "Code"
  7   12  Name and type     4   5  |  |    <init>  ()V
  8    1  String                   |  `--> "EmptyClass"
  9    1  String                   `-----> "java/lang/Object"

Dále je zajímavá sedmá položka, která obsahuje dva odkazy – odkaz na jméno (metody, atributu, rozhraní) a odkaz na typ. Oba odkazy musí směřovat na prvky typu řetězec:

  1   10  MethodRef         3   7          java/lang/Object.<init>()V
  2    7  Class             8              EmptyClass
  3    7  Class             9              java/lang/Object
  4    1  String              .----------> "<init>"
  5    1  String              |    .-----> "()V"
  6    1  String              |    |       "Code"
  7   12  Name and type     4-' 5--'       <init>  ()V
  8    1  String                           "EmptyClass"
  9    1  String                           "java/lang/Object"

Nejsložitější je hned první položka. Ta obsahuje informaci o metodě. Tato informace je rozdělena na dvě reference. První reference ukazuje na jméno třídy (řetězec se jménem třídy) a druhá reference ukazuje na prvek typu Name and type, který taktéž obsahuje dvě reference – viz též předchozí odstavec:

                                     .----------------------------------.
                                     |                                  |
  1   10  MethodRef         3-. 7----'     java/lang/Object.<init>()V   |
  2    7  Class             8 |            EmptyClass                   |
  3    7  Class             9 `----------> java/lang/Object             |
  4    1  String              .----------> "<init>"                     |
  5    1  String              |    .-----> "()V"                        |
  6    1  String              |    |       "Code"                       |
  7   12  Name and type     4-' 5--'       <init>  ()V <----------------'
  8    1  String                           "EmptyClass"
  9    1  String                           "java/lang/Object"

O jakou metodu se však v tomto případě jedná, když je naše třída (zdánlivě) prázdná? Jak jste již možná uhodli, tak naše třída ve skutečnosti při své inicializaci bude volat konstruktor předka a konstruktory se v bajtkódu pojmenovávají <init>. Jedná se o jméno, které nelze ve zdrojovém kódu použít, proto nikdy nedojde ke kolizím s uživatelskou metodou či atributem.

4. 32bitové číselné konstanty použité v programovém kódu

Již z předchozího příkladu je pravděpodobně patrné, že constant pool může obsahovat poměrně velké množství důležitých informací, a to i pro relativně jednoduché třídy. Má to svůj význam, protože v instrukční sadě se mnohé instrukce odkazují právě na prvky umístěné v constant poolu, což na jednu stranu vede k větší „informační hustotě“ instrukční sady, tak i k tomu, že se v sadě nenachází žádné přímé volání (zejména cizích) metod, protože v JVM lze až za běhu aplikace určit (pomocí classloaderů), které třídy se mají nahrát a dynamicky slinkovat. Jak již víme z předchozí části tohoto seriálu (i z úvodní kapitoly), jsou v constant poolu umístěny i celočíselné konstanty, popř. konstanty čísel reprezentovaných v systému plovoucí řádové čárky (floating point). Nejjednodušší je situace v případě, kdy jsou ve zdrojovém kódu třídy použity numerické konstanty typu int a float, tak, jak je tomu u naší další demonstrační třídy pojmenované ConstantsTest1:

public class ConstantsTest1 {
    public static final int   integerField = 400000;
    public static final float floatField   = 123.456f;
}

Obsah constant poolu je již poněkud složitější, prozatím však budeme některé položky ignorovat (tyto nové položky jsou určeny pro uložení jména a typu obou deklarovaných atributů, s devíti dalšími položkami jsme se již setkali v předchozí kapitole):

Magicka konstanta: cafebabe
Cislo verze:       50
Cislo podverze:    0

Velikost const. poolu: 16 prvku
  1   10  MethodRef         3  14    java/lang/Object.<init>()V
  2    7  Class            15        ConstantsTest1
  3    7  Class            16        java/lang/Object
  4    1  String                     "integerField"
  5    1  String                     "I"
  6    1  String                     "ConstantValue"
  7    3  Integer                    400000
  8    1  String                     "floatField"
  9    1  String                     "F"
 10    4  Float                      123.456001
 11    1  String                     "<init>"
 12    1  String                     "()V"
 13    1  String                     "Code"
 14   12  Name and type    11  12    <init>  ()V
 15    1  String                     "ConstantsTest1"
 16    1  String                     "java/lang/Object"

V této chvíli nás zajímá pouze dvojice položek s konstantami – jedná se o položku číslo sedm a deset:

  7    3  Integer                    400000
 10    4  Float                      123.456001

Jak můžete sami vidět, jedná se vlastně o ten nejjednodušší typ záznamu uloženého v constant poolu. Fyzická podoba těchto konstant je zřejmá: za tagem (s číslem 3 nebo 4) jsou uloženy čtyři bajty představující buď 32bitové celé číslo se znaménkem (bezznaménkové celočíselné konstanty ani proměnné Java nezná), nebo taktéž 32bitové reálné číslo jednoduché přesností. Pořadí uložení těchto čtyř bajtů je vždy big endian.

5. 8bitové, 16bitové a 64bitové číselné konstanty

Zatímco u číselných konstant typu int a float byla situace velmi jednoduchá, u číselných konstant dalších typů již můžeme narazit na několik zajímavostí. První zajímavostí je, že celočíselné typy s menším rozsahem než int jsou v constant poolu uloženy poněkud neúsporně jako int, což znamená, že i osmibitová konstanta typu byte je uložena jako pětibajtová položka (prvním bajtem je tag, další čtyři bajty představují 32bitové celé číslo se znaménkem). Podobné je to i u konstant typu short a char. Pokud by však byla takto malá celočíselná konstanta použita pouze někde ve výrazu a nikoli pro deklaraci a inicializaci třídního atributu, nemusí být v constant poolu uložena vůbec, protože pro práci s „malými“ konstantami slouží instrukce typu sipush či bipush (viz též další díl tohoto seriálu, v němž se konečně dostaneme i přímo k instrukční sadě používané virtuálním strojem Javy).

Zbývají nám tedy ještě celá 64bitová čísla typu long a čísla s plovoucí řádovou čárkou s dvojitou přesností double. Ty jsou v constant poolu taktéž uloženy přímo jako konstanty, ovšem ihned za těmito položkami je nutné při vytváření obrazu constant poolu v operační paměti další položku bajtkódu vynechat. Pokud by se na tuto (vpravdě nelogickou!) operaci zapomnělo, byly by všechny reference/odkazy na položky v constant poolu špatně očíslované (navíc i celkový počet položek constant poolu, který je uložen v bajtkódu, s těmito výplněmi počítá). To je taktéž důvod pro to, aby funkce read_constant_po­ol_entry() z naší jednoduché utility vracela číslo určující počet položek, o kolik je nutné se ve vytvářeném obrazu constant poolu posunout. Podívejme se konečně na demonstrační příklad:

public class ConstantsTest2 {
    public static final long   longField   = 400000;
    public static final double doubleField = 123.456f;
    public static final byte   byteField   = 100;
    public static final short  shortField  = 10000;
    public static final char   charField   = 20000;
}

Obsah constant poolu vypadá následovně (ve skutečnosti se výplňové položky v bajtkódu neukládají, v paměti je však musíme vytvářet):

Magicka konstanta: cafebabe
Cislo verze:       50
Cislo podverze:    0

Velikost const. poolu: 27 prvku
  1   10  MethodRef         3  25    java/lang/Object.<init>()V
  2    7  Class            26        ConstantsTest2
  3    7  Class            27        java/lang/Object
  4    1  String                     "longField"
  5    1  String                     "J"
  6    1  String                     "ConstantValue"
  7    5  Long                       400000
  8    0  Neznamy tag
  9    1  String                     "doubleField"
 10    1  String                     "D"
 11    6  Double                     123.456001
 12    0  Neznamy tag
 13    1  String                     "byteField"
 14    1  String                     "B"
 15    3  Integer                    100
 16    1  String                     "shortField"
 17    1  String                     "S"
 18    3  Integer                    10000
 19    1  String                     "charField"
 20    1  String                     "C"
 21    3  Integer                    20000
 22    1  String                     "<init>"
 23    1  String                     "()V"
 24    1  String                     "Code"
 25   12  Name and type    22  23    <init>  ()V
 26    1  String                     "ConstantsTest2"
 27    1  String                     "java/lang/Object"

Pro nás jsou v tuto chvíli zajímavé pouze následující položky, k nimž jsem doplnil komentáře:

  7    5  Long                       400000
  8    0  Neznamy tag                             *** výplň ihned za "long"
 11    6  Double                     123.456001
 12    0  Neznamy tag                             *** výplň ihned za "double"
 15    3  Integer                    100
 18    3  Integer                    10000        *** pro konstantu typu short
 21    3  Integer                    20000        *** pro konstantu typu char

6. Způsob určení datových typů atributů a návratových hodnot metod

Další důležité informace, které jsou v constant poolu uložené, se týkají typů atributů tříd, popř. návratových typů metod i datových typů argumentů metod. Informace o všech těchto datových typech jsou určeny řetězcem, který se v případě primitivních datových typů skládá z jediného znaku (velkého písmena „B“, „C“, „D“, „F“, „I“, „J“, „S“ a „Z“) a v případě referenčních typů z plného jména třídy, před nějž je vložen znak „L“ a jméno třídy je ukončeno středníkem. Speciálním případem jsou pole (která jsou v Javě taktéž považována za referenční typy). V případě polí je v řetězci zakódován jak počet dimenzí, tak i datové typy prvků, ze kterých se pole skládá (může se samozřejmě jednat jak o primitivní datové typy, tak i o referenční typy). Podívejme se nejdříve na znaky použité v řetězci, pomocí něhož se specifikuje datový typ atributů, návratový typ metod i typy jednotlivých argumentů metod:

# Znak/řetězec Datový typ Význam
1 B byte osmibitové celé číslo se znaménkem
2 C char znak reprezentovaný v Unikódu
3 D double číslo s plovoucí řádovou čárkou s dvojitou přesností
4 F float číslo s plovoucí řádovou čárkou s jednoduchou přesností
5 I int 32bitové celé číslo se znaménkem
6 J long 64bitové celé číslo se znaménkem
7 Ljméno_třídy; reference instance třídy zadaného jména
8 S short 16bitové celé číslo se znaménkem
9 Z boolean pravdivostní hodnota true/false
10 [ reference jednodimenzionální pole

Podívejme se na jednoduchý příklad. Jedná se o třídu obsahující tři atributy – skalární celočíselnou proměnnou nazvanou promenna, jednorozměrné pole s prvky typu int nazvané pole a konečně dvourozměné pole s prvky typu int nazvané matice:

public class Test {
    public int     promenna;
    public int[]   pole;
    public int[][] matice;
}

Po překladu této třídy do bajtkódu najdeme v jeho constant poolu mj. i následujících šest položek, které popisují všechny tři atributy:

  4    1  String                     "promenna"
  5    1  String                     "I"
  6    1  String                     "pole"
  7    1  String                     "[I"
  8    1  String                     "matice"
  9    1  String                     "[[I"

Jak jsou tyto položky navzájem propojeny si řekneme příště, nás však v tuto chvíli zajímají především signatury atributů. V souladu s tabulkou uvedenou na začátku této kapitoly je skalární celočíselný atribut popsán řetězcem „I“. Jednorozměrné pole celočíselných prvků je popsáno řetězcem „[I“ (znak „[“ signalizuje, že se jedná o pole. Popis dvojrozměrného pole celočíselných prvků je určen řetězcem „[[I“, což lze číst jako pole polí celých čísel.

Co se stane, pokud použijeme namísto celočíselného datového typu typ referenční? Proveďme v kódu nepatrnou změnu:

public class Test {
    public String     promenna;
    public String[]   pole;
    public String[][] matice;
}

constant poolu v tomto případě dojde ke změnám, které opět odpovídají informacím uvedeným v tabulce uvedené na začátku této kapitoly – bude zde uloženo plně kvalifikované jméno třídy s prefixem „L“ a ukončujícím znakem středník:

  4    1  String                     "promenna"
  5    1  String                     "Ljava/lang/String;"
  6    1  String                     "pole"
  7    1  String                     "[Ljava/lang/String;"
  8    1  String                     "matice"
  9    1  String                     "[[Ljava/lang/String;"

7. Demonstrační příklad – výpis návratových typů metod

V dalším demonstračním příkladu si řekneme, jakým způsobem lze z constant poolu zjistit návratové typu metod. Ve třetí kapitole jsme si ukázali, jak je možné získat celou informaci o metodě, která je někde uvnitř třídy buď implicitně nebo explicitně volána. Připomeňme si, že informace o volané metodě je uložena v položce typu MethodRef, která obsahuje dva odkazy. První odkaz vede na položku typu Class s plným jménem třídy (přesněji řečeno s odkazem na řetězec nesoucí jméno třídy) a druhý odkaz vede na položku typu Name and type obsahující název metody, typy jejich parametrů (ne jejich jména) a taktéž typ návratové hodnoty. V následujícím testovacím příkladu je vytvořeno sedm bezparametrických metod, které vracejí hodnoty různých datových typů. Metody je v programovém kódu nutné zavolat, jinak by se v constant poolu neobjevily všechny podstatné informace. Volání je zařízeno v metodě main:

public class Methods2 {
    public void    methodReturningVoid() {}
    public int     methodReturningInteger() {return 1;}
    public byte    methodReturningByte()    {return 1;}
    public float   methodReturningFloat()   {return 1.0f;}
    public double  methodReturningDouble()  {return 1.0;}
    public int[]   methodReturningArray()   {return null;}
    public Object  methodReturningObject()  {return null;}

    public static void main(String[] args) {
        Methods2 m = new Methods2();
        m.methodReturningVoid();
        m.methodReturningInteger();
        m.methodReturningByte();
        m.methodReturningFloat();
        m.methodReturningDouble();
        m.methodReturningArray();
        m.methodReturningObject();
    }
}

Po překladu této třídy a použití pomocného nástroje z druhé kapitoly můžeme z constant poolu získat (samozřejmě kromě dalších informací) i sedm položek typu MethodRef:

  4   10  MethodRef         2  32    Methods2.methodReturningVoid()V
  5   10  MethodRef         2  33    Methods2.methodReturningInteger()I
  6   10  MethodRef         2  34    Methods2.methodReturningByte()B
  7   10  MethodRef         2  35    Methods2.methodReturningFloat()F
  8   10  MethodRef         2  36    Methods2.methodReturningDouble()D
  9   10  MethodRef         2  37    Methods2.methodReturningArray()[I
 10   10  MethodRef         2  38    Methods2.methodReturningObject()Ljava/lang/Object;

Z tohoto výpisu je patrné, že virtuální stroj Javy má k dispozici jak plný název třídy, tak i název metody společně s jejím návratovým typem. Ten je v našem případě součástí referencovaných položek typu Name and type, z nichž vede odkaz na řetězec s typem parametrů (zde pouze prázdné závorky) i návratovým typem umístěným za závorkami. Způsob „pojmenování“ návratového datového typu je shodný se způsobem popsaným v předchozí kapitole s tím rozšířením, že metody, které nevrací žádná data, mají nastaven návratový typ „V“ jako void (ten samozřejmě nebyl u třídních atributů použit).

Pro úplnost ještě následuje výpis obsahu celého constant poolu, všechny důležité položky však byly vysvětleny výše:

Magicka konstanta: cafebabe
Cislo verze:       50
Cislo podverze:    0

Velikost const. poolu: 39 prvku
  1   10  MethodRef        11  30    java/lang/Object.<init>()V
  2    7  Class            31        Methods2
  3   10  MethodRef         2  30    Methods2.<init>()V
  4   10  MethodRef         2  32    Methods2.methodReturningVoid()V
  5   10  MethodRef         2  33    Methods2.methodReturningInteger()I
  6   10  MethodRef         2  34    Methods2.methodReturningByte()B
  7   10  MethodRef         2  35    Methods2.methodReturningFloat()F
  8   10  MethodRef         2  36    Methods2.methodReturningDouble()D
  9   10  MethodRef         2  37    Methods2.methodReturningArray()[I
 10   10  MethodRef         2  38    Methods2.methodReturningObject()Ljava/lang/Object;
 11    7  Class            39        java/lang/Object
 12    1  String                     "<init>"
 13    1  String                     "()V"
 14    1  String                     "Code"
 15    1  String                     "methodReturningVoid"
 16    1  String                     "methodReturningInteger"
 17    1  String                     "()I"
 18    1  String                     "methodReturningByte"
 19    1  String                     "()B"
 20    1  String                     "methodReturningFloat"
 21    1  String                     "()F"
 22    1  String                     "methodReturningDouble"
 23    1  String                     "()D"
 24    1  String                     "methodReturningArray"
 25    1  String                     "()[I"
 26    1  String                     "methodReturningObject"
 27    1  String                     "()Ljava/lang/Object;"
 28    1  String                     "main"
 29    1  String                     "([Ljava/lang/String;)V"
 30   12  Name and type    12  13    <init>  ()V
 31    1  String                     "Methods2"
 32   12  Name and type    15  13    methodReturningVoid  ()V
 33   12  Name and type    16  17    methodReturningInteger  ()I
 34   12  Name and type    18  19    methodReturningByte  ()B
 35   12  Name and type    20  21    methodReturningFloat  ()F
 36   12  Name and type    22  23    methodReturningDouble  ()D
 37   12  Name and type    24  25    methodReturningArray  ()[I
 38   12  Name and type    26  27    methodReturningObject  ()Ljava/lang/Object;
 39    1  String                     "java/lang/Object"

8. Demonstrační příklad – výpis typu argumentů metod

Zbývá nám již popsat poslední maličkost – způsob určení datových typů argumentů metod. Jedná se o velmi důležitý údaj, protože právě na základě těchto informací je určeno, která (potenciálně přetížená) metoda se ve skutečnosti zavolá. Toto rozhodnutí provádí překladač, který volání konkrétní metody přeloží do instrukce invokevirtual. Podívejme se nyní na zdrojový kód demonstračního příkladu, v němž je jedenáct metod s různým počtem a typy argumentů. Metody jsou opět volány z metody main, aby se v constant poolu objevily všechny důležité informace:

public class Methods3 {
    public void noParams() {}
    public void oneInteger(int x) {}
    public void twoIntegers(int x, int y) {}
    public void twoFloats(float x, float y) {}
    public void intArray(int[] a) {}
    public void int2DArray(int[][] a) {}
    public void int3DArray(int[][][] a) {}
    public void twoArrays(int[] a, float[] b) {}
    public void object(Object o) {}
    public void objectArray(Object[] o) {}
    public void object2DArray(Object[][] o) {}

    public static void main(String[] args) {
        Methods3 m = new Methods3();
        m.noParams();
        m.oneInteger(1);
        m.twoIntegers(1,2);
        m.twoFloats(1.0f, 2.0f);
        m.intArray(null);
        m.int2DArray(null);
        m.int3DArray(null);
        m.twoArrays(null, null);
        m.object(null);
        m.objectArray(null);
        m.object2DArray(null);
    }
}

V bajtkódu můžeme jednoduše nalézt informace o všech jedenácti metodách:

  4   10  MethodRef         2  47    Methods3.noParams()V
  5   10  MethodRef         2  48    Methods3.oneInteger(I)V
  6   10  MethodRef         2  49    Methods3.twoIntegers(II)V
  7   10  MethodRef         2  50    Methods3.twoFloats(FF)V
  8   10  MethodRef         2  51    Methods3.intArray([I)V
 10   10  MethodRef         2  53    Methods3.int2DArray([[I)V
 12   10  MethodRef         2  55    Methods3.int3DArray([[[I)V
 13   10  MethodRef         2  56    Methods3.twoArrays([I[F)V
 14   10  MethodRef         2  57    Methods3.object(Ljava/lang/Object;)V
 15   10  MethodRef         2  58    Methods3.objectArray([Ljava/lang/Object;)V
 17   10  MethodRef         2  60    Methods3.object2DArray([[Ljava/lang/Object;)V

Pro tuto chvíli nás nemusí zajímat návratové typy, protože ty jsou ve všech případech zakódovány znakem „V“ (void). Důležitější jsou typy argumentů. Jak je z výpisu constant poolu patrné, jsou typy argumentů umístěny do závorek, čímž je automaticky určen i jejich počet. Metoda bez parametrů obsahuje pouze prázdné závorky (stejně jako v samotné Javě), metoda s jedním parametrem obsahuje řetězec shodný s typem atributu (nesmí se zde objevit „V“) a pokud má metoda více parametrů, jsou jejich typy jednoduše vypsány za sebou bez použití oddělovače – ten není zapotřebí. Příkladem může být metoda:

public void twoArrays(int[] a, float[] b) {}

jejíž reference vypadá v bajtkódu následovně:

Cloud 24 - tip 1

 13   10  MethodRef         2  56    Methods3.twoArrays([I[F)V

Řetězec v závorkách můžeme přečíst jako „pole prvků typu int následované polem prvků typu float“.

Pro úplnost bude opět vypsán obsah celého constant poolu, z něhož je patrné, že právě constant pool v naprosté většině případů tvoří největší objem bajtkódu:

Magicka konstanta: cafebabe
Cislo verze:       50
Cislo podverze:    0

Velikost const. poolu: 61 prvku
  1   10  MethodRef        18  45    java/lang/Object.<init>()V
  2    7  Class            46        Methods3
  3   10  MethodRef         2  45    Methods3.<init>()V
  4   10  MethodRef         2  47    Methods3.noParams()V
  5   10  MethodRef         2  48    Methods3.oneInteger(I)V
  6   10  MethodRef         2  49    Methods3.twoIntegers(II)V
  7   10  MethodRef         2  50    Methods3.twoFloats(FF)V
  8   10  MethodRef         2  51    Methods3.intArray([I)V
  9    7  Class            52        [[I
 10   10  MethodRef         2  53    Methods3.int2DArray([[I)V
 11    7  Class            54        [[[I
 12   10  MethodRef         2  55    Methods3.int3DArray([[[I)V
 13   10  MethodRef         2  56    Methods3.twoArrays([I[F)V
 14   10  MethodRef         2  57    Methods3.object(Ljava/lang/Object;)V
 15   10  MethodRef         2  58    Methods3.objectArray([Ljava/lang/Object;)V
 16    7  Class            59        [[Ljava/lang/Object;
 17   10  MethodRef         2  60    Methods3.object2DArray([[Ljava/lang/Object;)V
 18    7  Class            61        java/lang/Object
 19    1  String                     "<init>"
 20    1  String                     "()V"
 21    1  String                     "Code"
 22    1  String                     "noParams"
 23    1  String                     "oneInteger"
 24    1  String                     "(I)V"
 25    1  String                     "twoIntegers"
 26    1  String                     "(II)V"
 27    1  String                     "twoFloats"
 28    1  String                     "(FF)V"
 29    1  String                     "intArray"
 30    1  String                     "([I)V"
 31    1  String                     "int2DArray"
 32    1  String                     "([[I)V"
 33    1  String                     "int3DArray"
 34    1  String                     "([[[I)V"
 35    1  String                     "twoArrays"
 36    1  String                     "([I[F)V"
 37    1  String                     "object"
 38    1  String                     "(Ljava/lang/Object;)V"
 39    1  String                     "objectArray"
 40    1  String                     "([Ljava/lang/Object;)V"
 41    1  String                     "object2DArray"
 42    1  String                     "([[Ljava/lang/Object;)V"
 43    1  String                     "main"
 44    1  String                     "([Ljava/lang/String;)V"
 45   12  Name and type    19  20    <init>  ()V
 46    1  String                     "Methods3"
 47   12  Name and type    22  20    noParams  ()V
 48   12  Name and type    23  24    oneInteger  (I)V
 49   12  Name and type    25  26    twoIntegers  (II)V
 50   12  Name and type    27  28    twoFloats  (FF)V
 51   12  Name and type    29  30    intArray  ([I)V
 52    1  String                     "[[I"
 53   12  Name and type    31  32    int2DArray  ([[I)V
 54    1  String                     "[[[I"
 55   12  Name and type    33  34    int3DArray  ([[[I)V
 56   12  Name and type    35  36    twoArrays  ([I[F)V
 57   12  Name and type    37  38    object  (Ljava/lang/Object;)V
 58   12  Name and type    39  40    objectArray  ([Ljava/lang/Object;)V
 59    1  String                     "[[Ljava/lang/Object;"
 60   12  Name and type    41  42    object2DArray  ([[Ljava/lang/Object;)V
 61    1  String                     "java/lang/Object"

9. Odkazy na Internetu

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

Byl pro vás článek přínosný?