Java Virtual Mchine Aplikační programování v Javě (BI-APJ) - 3 Ing. Jiří Daněček Katedra softwarového inženýrství Fakulta informačních technologií ČVUT Praha Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti
Motivace Struktura kompilátoru: přední část (foreground) - kontrola syntaxe a sémantiky, optimalizace, překlad do vnitřní formy, vnitřní forma (intermediate language) - typicky formát "programu" pro abstraktní (virtuální) počítač se zásobníkovou architekturou, zadní část (background, generátor kódu) - překlad z vnitřní formy do strojového kódu procesoru. Vnitřní forma může být také "naivně" přímo interpretována: nevýhoda - řádové zpomalení, oproti generovanému kódu (na interpretaci každé instrukce je potřeba zavolat metodu), výhoda - interpret je mnohem jednodušší než generátor kódu a může být společný pro různé platformy. Vnitřní formy obvykle nebývají standardizovány, ale každý kompilátor definuje svou vlastní => větší nároky na vývoj
JVM JVM - Java Virtual Machine: specifikace popisující architekturu virtuálního počítače, jehož "strojový kód" je tvořen bytekódem, vlastní program provádějící interpretaci bytekódu (java). Implementace JVM: HotSpot, KVM (Sun), JRockit (Oracle), J9 (IBM),... JVM obsahuje (resp. definuje): paměťové prostory virtuálního počítače, formát a význam instrukcí bytecódu formát souborů.class Literatura: http://en.wikipedia.org/wiki/java_virtual_machine Java SE specifications
Společné paměťové prostory JVM Společné pro celou JVM: Heap - prostor pro alokaci instancí tříd a polí. Velikost je možno nastavit parametrem (-Xms, -Xmn). Vyčerpání: OutOfMemoryError Oblast metod (Method area) - obsahuje data specifická pro třídy: kód metod, konstanty, atp. Obdoba kódového segmentu v klasických (kompilovaných) systémech. Součástí oblasti metod je: Blok konstant (Runtime constant pool) - je vytvořen pro každou třídu (resp. rozhraní). Je kombinace tabulky konstant a tabulky symbolů v klasických systémech. Skládá se ze jednolivých položek (entry) označených indexy. Položky jsou: 1. hodnoty konstant (String, double,...) 2. symbolické reference na třídy, položky a metody
Paměťové prostory pro thread PC register - obsahuje adresu právě vykonávané instrukce, JVM stack (pevné velikosti nebo expandující) - obsahuje rámy aktivních metod. JVM stack nemusí být souvislý a rámy mohou být alokovány na heapu. (StackOverflowError, OutOfMemoryError). Native method stack (C-stack) - slouží pro práci nativních metod.
Rám Rám je vytvořen v okamžiku, kdy je metoda zavolaná a zrušen v okamžiku jejího dokončení. Rám obsahuje: pole lokálních proměnných, zásobník operandů, referenci na constant pool třídy metody, Pole lokálních proměnných je tvořeno 32bitovými položkami (long a double obsazují dvě položky). Proměnné jsou identifikovány indexy. Argumenty (skutečné parametry) jsou uloženy v lokálních proměnných indexu 0 až N Zásobník operandů slouží k výpočtu výrazů. Instrukce bytekódu pracují nad zásobníkem operandů. Operand na zásobníku může být libovolného typu (včetně long a double) Velikost pole lokálních proměnných i velikost zásobníku operandů jsou určené při překladu. Rám právě prováděné (aktuální) metody se nazývá aktuální.
Příklad rámu Pro následující metodu: long m(long x) { int y = 4; return x + y; } která byla zavolána s parametrem -1, má rám před dokončením metody tvar:
Bytekód Instrukce bytekódu se skládá z jednobytového operačního kódu a eventuálních operandů. Většina instrukcí je typovaná - obsahuje identifikaci typu začátečním písmenem operačního kódu: b-byte, s-short, c- char, i - int, l - long, f - float, d - double, a - reference Omezený počet instrukcí nemožňuje ortogonální řešení (tj. každý typ s každou instrukcí). Vzhledem k celočíselnému povýšení existují pro typ byte a short pouze instrukce: uložení konstany na zásobník: bipush, sipush čtení a uložení hodnoty do prvku pole: baload, saload konverzní instrukce
Typy instrukcí Instrukce load, store - přesouvají hodnoty mezi operandovým zásobníkem a lokálními proměnnými (parametry), Instrukce push, ldc, const - ukládají konstantu na zásobník, Aritmetické instrukce Relační instrukce Konverzní instrukce Vytváření objektů, Přístup k položkám Práce s polem Podmíněné skoky Další řídící instrukce Instrukce volání metod
Parametry instrukcí V následujícím popisu jsou pro parametry instrukcí použity tyto zkratky: uind1-1-bytový bezznaménkový index lokální proměnné, ucp1, ucpi2-1 resp. 2-bytový bezznaménkový index do oblasti konstant (pole constant-pool), typ1-1-bytový parametr udávající primitivní typ, udim1-1-bytový bezznaménkový počet dimenzí pole, sconst1, sconst2-1 resp. 2-bytová konstanta se znaménkem, soffset2, soffset4-2 resp. 4-bytový offset se znaménkem mezi adresou aktuální a cílové instrukce.
Instrukce load iload uind1, lload uind1 fload uind1, dload uind1, aload uind1 - instrukce přesune operand z lokální proměnné určené parametrem uind1 na zásobník:... =>..., value iload_n, lload_n, fload_n, dload_n, aload_n - instrukce přesune operand z lokální proměnné indexu n (0 až 3) na zásobník:... =>..., value
Instrukce store istore uind1, lstore uind1 fstore uind1, dstore uind1, astore uind1 - instrukce přesune operand ze zásobníku do lokální proměnné určené parametrem uind8:..., value =>... istore_n, lstore_n, fstore_n, dstore_n, astore_n - instrukce přesune operand ze zásobníku do lokální proměnné indexu n (n je 0 až 3):..., value =>...
Instrukce push a ldc bipush sconst1 - instrukce rozšíří (sign extends) svůj parametr typu byte a uloží jej na zásobník:... =>..., value sipush sconst2 - instrukce rozšíří (sign extends) svůj parametr typu short a uloží jej na zásobník:... =>..., value ldc ucpi1, ldc_w ucpi2 - instrukce uloží na zásobník konstantu z konstant-poolu:... =>..., value ldc2_w ucpi2 - instrukce uloží na zásobník konstantu double nebo long z konstant-poolu:... =>..., value
Instrukce const aconst_null - uloží na zásobník hodnotu null:... =>..., null iconst_m1 - uloží na zásobník hodnotu -1 typu int:... =>..., -1 iconst_0,..., iconst_5 - uloží na zásobník hodnotu 0, 1, 2, 3, 4 resp. 5 typu int:... =>..., value lconst_0, lconst_1 - uloží na zásobník hodnotu 0L resp. 1L typu long:... =>..., value fconst_0, fconst_1, fconst_2 - instrukce uloží na zásobník hodnotu 0.0f, 1.0f resp., 2.0f typu float:... =>..., value dconst_0, dconst_1 - uloží hodnotu typu double:... =>..., value
Aritmetické instrukce Instrukce (nemají operandy) provedou operaci mezi operandy na vrcholu operandového zásobníku a výsledek vrátí na zásobník: iadd, ladd, fadd, dadd isub, lsub, fsub, dsub imul, lmul, fmul, dmul idiv, ldiv, fdiv, ddiv irem, lrem, frem, drem ineg, lneg, fneg, dneg ishl, ishr, iushr, lshl, lshr, lushr ior, lor iand, land ixor, lxor
Relační instrukce dcmpg, dcmpl - uloží na zásobník výsledek porovnání dvou operandů typu double (-1, 0, 1):..., value1, value2 =>..., result fcmpg, fcmpl - uloží na zásobník výsledek porovnání dvou operandů typu float (-1, 0, 1):..., value1, value2 =>..., result, lcmp - uloží na zásobník výsledek porovnání dvou operandů typu long (-1, 0, 1):..., value1, value2 =>..., result
Konverzní instrukce i2l, i2f, i2d, l2f, l2d, f2d - instrukce provádějící rozšiřující konverzi:..., value =>..., result i2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l, d2f - instrukce provádějící zužující konverzi:..., value =>..., result
Manipulace se zásobníkem pop - odstranění operandu ze zásobníku:..., value =>... pop2 - odstranění operandu typu long nebo double ze zásobníku:..., value =>... dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2 - duplikování operandu na zásobníku swap - prohození operandů na zásobníku:..., value1, value2 =>..., value2, value1
Vytváření objektů new ucpi2 - vytvoření instance třídy dané parametrem ucpi2:... =>..., objectref newarray typ1 - vytvoření pole prvků primitivních typu určeného parametrem typ1:..., count =>..., arrayref anewarray ucpi2 - vytvoření pole referencí na typ daný parametrem ucpi2:..., count =>..., arrayref multianewarray ucpi2, udim1 - vytvoření vícerozměrného pole:..., count1, count2,... =>..., arrayref
Přístup k položkám getstatic ucpi2 - přesun hodnoty třídní proměnné určené parametrem ucpi2 na zásobník:..., =>..., value putstatic ucpi2 - přesun hodnoty ze zásobníku do třídní proměnné určené parametrem ucpi2:..., value =>..., getstatic ucpi2 - přesun hodnoty třídní proměnné určené parametrem ucpi2 na zásobník:..., =>..., value putstatic ucpi2 - přesun hodnoty ze zásobníku do třídní proměnné určené parametrem ucpi2:..., value =>...,
Práce s polem baload, caload, saload, iaload, laload, faload, daload, aaload - přesun hodnoty prvku pole na zásobník:..., arayref, index =>..., value bastore, castore, sastore, iastore, lastore, fastore, dastore, aastore - uložení hodnoty ze zásobníku do prvku pole :..., arayref, index, value =>..., arraylength - délka pole:..., arayref =>..., length
Ostatní instrukce iinc uind1, sconst1 - instrukce přičte hodnotu parametru sconst1 k lokální proměnné o indexu uind1. instanceof, checkcast
Podmíněné skoky ifeq soffset2, ifne soffset2, iflt soffset2, ifle soffset2, ifgt soffset2, ifge soffset2 - podmíněné skoky podle relace hodnoty na vrcholu zásobníku s 0:..., value =>... ifnull soffset2, ifnonnull soffset2 - podmíněné skoky podle hodnoty reference na vrcholu zásobníku:..., value =>... if_icmpeq soffset2, if_icmpne soffset2, if_icmplt soffset2, if_icmpgt soffset2, if_icmple soffset2, if_icmpge soffset2, if_acmpeq soffset2, if_acmpne soffset2 - podmíněné skoky podle relace mezi dvěma operandy typu int na vrcholu zásobníku:..., value1, value2 =>...
Další řídící instrukce tableswitch... - skok v příkazu switch podle indexu:..., index =>... lookupswitch... - skok v příkazu switch podle klíče:..., key =>... goto soffset2, goto_w soffset4 - nepodmíněný skok o offset soffset2 resp. soffset4, jsr soffset2, jsr_w soffset4 - skok do klauzule finally o offset soffset2 resp. soffset4, ret uind1 - návrat z klauzule finally athrow - vržení výjimky, jejíž reference je uložena na vrcholu zásobníku:..., objectref =>..., objectref
Volání metod invokevirtual ucpi2 - volání instanční metody určené parametrem ucpi2: objectref, arg1, arg2,..., argn =>... invokeinterface ucpi2 - volání metody rozhraní určené parametrem ucpi2: objectref, arg1, arg2,..., argn =>... invokespecial ucpi2 - volání privátní metody resp. konstruktoru určené parametrem ucpi2: objectref, arg1, arg2,..., argn =>... invokestatic ucpi2 - volání třídní metody určené parametrem ucpi2: arg1, arg2,..., argn =>...
Formát class souboru Class soubory (classfile) jsou binární soubory s proměnnou délkou vět. Formát je popsán modifikovaným zápisem struktur jazyka C, které obsahují "pole" položek proměnné délky. Prvky těchto "polí" jsou referencovány "indexy" (tj. pořadovým číslem v rámci pole). Index se používá k identifikaci položky (viz např. parametry instrukcí bytekódu ucpi2) Kořenová struktura ClassFile obsahuje pole: constant_pool - pole symbolů a konstamt interfaces - pole implemetovaných interfaců fields methods attributes
Formát class souboru ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
Pole constant-pool Pole constant-pool obsahuje struktury, které reprezentují: symbolické reference na třídy: CONSTANT_Class_info symbolické reference na položky: CONSTANT_Fieldref_info, symbolické reference na metody: CONSTANT_Methodref_info, CONSTANT_InterfaceMethodref_info literály: CONSTANT_String_info, CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info, CONSTANT_Double_info pomocné struktury: CONSTANT_NameAndType_info, CONSTANT_Utf8_info
Struktury symbolických referencí Struktury popisují symbolické odkazy na položky a metody. Indexy na tyto struktury se používají např v instrukcích putfield, getfield atd. CONSTANT_Fieldref_info { u1 tag; ucpi2 class_index; ucpi2 name_and_type_index; } CONSTANT_Methodref_info { u1 tag; ucpi2 class_index; ucpi2 name_and_type_index; } CONSTANT_InterfaceMethodref_info { u1 tag; ucpi2 class_index; ucpi2 name_and_type_index; }
Struktury literálů Struktury CONSTANT_String_info, CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info, CONSTANT_Double_info reprezentují literály: CONSTANT_String_info { u1 tag; ucpi2 string_index; } CONSTANT_Integer_info { u1 tag; u4 bytes; }... CONSTANT_Double_info { u1 tag; u4 high_bytes; u4 low_bytes; }
Pomocné struktury Struktura CONSTANT_NameAndType_info reprezentuje jméno a typ položky nebo metody: CONSTANT_NameAndType_info { u1 tag; ucpi2 name_index; ucpi2 descriptor_index; } Struktura CONSTANT_Utf8_info reprezentuje posloupnost znaků utf8: CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }
Pole fields Pole fields obsahuje struktury field_info, které reprezentují položky třídy: field_info { u2 access_flags; ucpi2 name_index; ucpi2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
Pole methods Pole methods obsahuje struktury method_info. Každá metoda (včetně inizializačních metod instancí a tříd) je popsána strukturou method_info: method_info { u2 access_flags; ucpi2 name_index; ucpi2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } Každá metoda, která není nativní nebo abstract má v poli attributes právě jeden atribut code obsahující vlastní kód metody.
Atribut code Atribut code obsahuje vlastní instrukce bytekódu (pole code), tabulku catch klauzulí (pole exception_table) a další informace o metodě: Code_attribute { ucpi2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
Reprezentuje typ proměnné: FieldDescriptor: FieldType ComponentType: FieldType FieldType: BaseType ObjectType Deskriptor položky ArrayType BaseType: B C D F I J (long) S Z (boolean) ObjectType: L <classname> ; ArrayType: [ ComponentType Příklad deskriptrů: proměnná typu Object: Ljava/lang/Object; proměnná typu double[][][]: [[[D
Deskriptor metody Deskriptor metody reprezentuje parametery a návratovou hodnotu metody: MethodDescriptor: ( ParameterDescriptor* ) ReturnDescriptor ParameterDescriptor: FieldType ReturnDescriptor: FieldType V Příklad: Object mymethod(int i, double d, Thread t) deskriptor: (IDLjava/lang/Thread;)Ljava/lang/Object;
Vykonání programu Vykonání programu se skládá z následujících kroků: Start JVM Zpracování tříd: Zavádění (loading) a vytváření tříd Linkování: verifikace, rezoluce, příprava Inicializace Exit JVM
Zavádění a vytváření tříd Vytváření třídy spočívá ve vytvoření její vnitřní reprezentace v oblasti metod a to v implementačně definovaném formátu. Před vytvořením musí být třída zavedena (load) pověřeným zavaděčem (class loader). Vytváření nějaké třídy C je vyvoláno: 1. uvedením C jako hlavní třídy při spuštění JVM, 2. jinou třídou (resp. rozhraním), která referencuje C ve své tabulce konstant, 3. explicitně pomocí reflexe, např: Class.forName(...). Třídy, které nejsou pole jsou vytvářeny ze své binární reprezentace v.class souborech. Třídy pole jsou vytvářeny přímo JVM.
Verifikace tříd Verifikace tříd probíhá v několika fázích: 1. fáze - základní verifikace: kontrola magického čísla, kontrola délek atributů, atd. 2. fáze - strukturální verifikace třída final nemá podtřídu každá třída má nadtřídu constant-pool je konzistetní validnost symbolických referencí, atd. 3. fáze - verifikace bytekódu: zásobník operandů má vždy stejný tvar pro libovolný průchod programem, lokální proměnné jsou před použitím definované, metody jsou volány s odpovídajícími argumenty, položkám jsou přiřazovány odpovídající typy hodnot, atd.
Rezoluce Rezoluce je proces určení konkrétních konstrukcí za symbolické odkazy v tabulce konstant. Rezolovat se musí odkazy na: 1. třídy - rezoluce znamená vytvoření třídy. Možná chyba: IllegalAccessError, 2. položky - nejprve musí proběhnout rezoluce třídy položky. Možné chyby: NoSuchFieldError, IllegalAccessError, 3. metody - nejprve musí proběhnout rezoluce třídy metody. Možné chyby: NoSuchMethodError, IllegalAccessError, AbstractMethodError Rezoluce může být provedena při linkování třídy (eager) a nebo může být odložena až na dobu, kdy se vykonává instrukce, která odkaz používá (lazy). Symbolické odkazy používají např. instrukce: new (na třídu), g (na položku), invokestatic (na třídu a metodu), atd.
Inicializace Inicializace znamená vykonání statických inicalizátorů a inicalizátorů statických položek deklarovaných ve třídě (resp. rozhraní). Kód inicializace je uveden v systémové metodě <clinit>. Před provedením inicalizace se musí inicializovat nadtřída (avšak ne implemetované rozhraní). Inicializace třídy je vyvolána: inicializací podtřídy, vytvořením instance třídy, zavoláním statické metody, přístupem k nekonstantní statické položce. Pozn. přístup ke konstantní (final )položce nevyvolá inicalizaci, protože jej kompilátor nahradí hodnotou položky.
void spin() { int i; for (i = 0; i < 100; i++) { // Loop body is empty } } Překlad do bytekódu Method void spin() 0 iconst_0 // push int konstantu 0 1 istore_1 // ulož do lokální proměnné 1 (i=0) 2 goto 8 // skok na konec cyklu 5 iinc 1 1 // inkrementace lokální proměnné 1 8 iload_1 // push lokální proměnné 1 (i) 9 bipush 100 // push int konstantu 100 11 if_icmplt 5 // porovnání a skok na začátek cyklu // je-li menší 14 return // návrat
int align2grain(int i, int grain) { return ((i + grain-1) & ~(grain-1)); } Method int align2grain(int,int) 0 iload_1 1 iload_2 2 iadd 3 iconst_1 4 isub 5 iload_2 6 iconst_1 7 isub 8 iconst_m1 9 ixor 10 iand 11 ireturn Aritmetika
Volání metod int add12and13() { return addtwo(12, 13); } Method int add12and13() 0 aload_0 // push lokální proměnou 0 (this) 1 bipush 12 // push int konstanta 12 3 bipush 13 // push int konstanta 13 5 invokevirtual #4 // volání addtwo 8 ireturn // vrácení hodnoty z vrcholu // operandového zásobníku