Sveiki atvykę į kitą „Under the Hood“ dalį. Šis stulpelis leidžia „Java“ kūrėjams pažvelgti į tai, kas vyksta po jų vykdomomis „Java“ programomis. Šio mėnesio straipsnyje iš pradžių apžvelgiamas „Java“ virtualiosios mašinos (JVM) baitų kodų instrukcijų rinkinys. Straipsnyje pateikiami primityvūs tipai, valdomi baitų kodais, baitai, konvertuojantys tarp tipų, ir baitekodai, kurie veikia rietuvėje. Vėlesniuose straipsniuose bus aptariami kiti baitų kodų šeimos nariai.
Baitų kodo formatas
Baitekodai yra „Java“ virtualiosios mašinos mašininė kalba. Kai JVM įkelia klasės failą, jis gauna po vieną kiekvienos klasės metodo baitekodų srautą. Baitų kodų srautai saugomi JVM metodo srityje. Metodo baitkodai vykdomi, kai tas metodas yra paleidžiamas vykdant programą. Jie gali būti įvykdyti atliekant intepretaciją, kompiliavimą laiku ir bet kuria kita technika, kurią pasirinko konkretaus JVM dizaineris.
Metodo baitų kodų srautas yra „Java“ virtualiosios mašinos instrukcijų seka. Kiekviena instrukcija susideda iš vieno baito opcode paskui nulį ar daugiau operandai. Opcode nurodo veiksmą, kurį reikia atlikti. Jei norint, kad JVM galėtų atlikti veiksmą, reikia daugiau informacijos, ši informacija yra užkoduota į vieną ar daugiau operandų, kurie iškart seka opkodą.
Kiekvienas opcode tipas turi mnemoniką. Įprastu surinkimo kalbos stiliumi „Java“ baitekodų srautus galima pavaizduoti jų memonika, po kurios pateikiamos bet kokios operando reikšmės. Pvz., Šį baitekodų srautą galima išardyti į mnemoniką:
„Bytecode“ srautas: 03 3b 84 00 01 1a 05 68 3b a7 ff f9 // Išardymas: iconst_0 // 03 istore_0 // 3b iinc 0, 1 // 84 00 01 iload_0 // 1a iconst_2 // 05 imul // 68 istore_0 // 3b goto -7 // a7 ff f9
Baitų kodų instrukcijų rinkinys buvo suprojektuotas taip, kad būtų kompaktiškas. Visos instrukcijos, išskyrus dvi, kuriose kalbama apie šuolį į stalą, yra suderintos su baitų ribomis. Bendras opkodų skaičius yra pakankamai mažas, todėl opkodai užima tik vieną baitą. Tai padeda sumažinti klasės failų, kurie gali būti keliaujama per tinklus, dydį prieš JVM įkėlimą. Tai taip pat padeda išlaikyti nedidelį JVM įgyvendinimo dydį.
Visas skaičiavimas JVM yra sutelktas į kaminą. Kadangi JVM neturi registrų, kuriuose būtų galima laikyti netikrąsias vertes, viskas turi būti nustumta į rietuvę, kad ją būtų galima naudoti skaičiuojant. Todėl „Bytecode“ instrukcijos pirmiausia veikia kamino. Pavyzdžiui, pirmiau pateiktoje baitų kodų sekoje vietinis kintamasis padauginamas iš dviejų, pirmiausia stumiant vietinį kintamąjį ant kamino su iload_0
instrukciją, tada stumkite du ant kamino su iconst_2
. Po to, kai abu sveikieji skaičiai yra nustumti į kaminą, imul
instrukcija efektyviai iššoka du skaičius iš kamino, padaugina juos ir vėl nustumia rezultatą į kaminą. Rezultatas iššoka nuo kamino viršaus ir saugomas atgal į vietinį kintamąjį istore_0
instrukcija. JVM buvo sukurtas kaip rietuvių mašina, o ne registrų pagrindu sukurta mašina, skirta palengvinti efektyvų registrų neturinčių architektūrų, tokių kaip „Intel 486“, įdiegimą.
Pirmykščiai tipai
JVM palaiko septynis primityvius duomenų tipus. „Java“ programuotojai gali deklaruoti ir naudoti šių duomenų tipų kintamuosius, o „Java“ baitekodai veikia pagal šiuos duomenų tipus. Septyni primityvūs tipai išvardyti šioje lentelėje:
Tipas | Apibrėžimas |
---|---|
baitas | vieno baito pasirašytas dviejų papildomas skaičius |
trumpas | dvibaitis pasirašė dviejų komplemento skaičių |
tarpt | 4 baitai pasirašė dviejų papildų skaičių |
ilgas | 8 baitai pasirašė dviejų papildų skaičių |
plūdė | 4 baitų „IEEE 754“ vieno tikslumo plūdė |
dvigubai | 8 baitų IEEE 754 dvigubo tikslumo plūdė |
char | 2 baitų nepasirašytas „Unicode“ simbolis |
Pirmykščiai tipai rodomi kaip operandai baitų kodų srautuose. Visi primityvūs tipai, užimantys daugiau nei 1 baitą, yra saugomi didžiosios eilės tvarka baitų kodų sraute, o tai reiškia, kad aukštesnės eilės baitai yra prieš žemesnės eilės baitus. Pavyzdžiui, norėdami pastumti pastoviąją vertę 256 (šešioliktainis 0100) ant kamino, naudosite sipushas
opcode, po kurio seka trumpas operandas. Trumpasis rodomas baitų kodų sraute, parodytame žemiau, kaip „01 00“, nes JVM yra didžiulis. Jei JVM būtų mažai baigtinis, trumpasis pasirodytų „00 01“.
„Bytecode“ srautas: 17 01 00 // Išardymas: sipush 256; // 17 01 00
„Java“ opcodai paprastai nurodo jų operandų tipą. Tai leidžia operandams būti tik savimi, nereikia nurodyti jų tipo JVM. Pvz., Užuot turėjęs vieną opkodą, kuris stumia vietinį kintamąjį į kaminą, JVM turi kelis. Opcodes iload
, apkrova
, apkrova
ir krautis
stumkite ant kamino atitinkamus int, long, float ir double tipo vietinius kintamuosius.
Konstantų stūmimas į kaminą
Daugelis opcodų pastumia konstantus ant kamino. Opcodes nurodo pastovią vertę stumti trimis skirtingais būdais. Nuolatinė reikšmė yra netiesioginė pačiame opkode, ji seka opkodą baitų kodo sraute kaip operandas arba yra paimama iš pastovaus telkinio.
Kai kurie opcodai savaime nurodo tipą ir pastovią vertę, kurią reikia stumti. Pavyzdžiui, iconst_1
„opcode“ nurodo JVM stumti sveikojo skaičiaus pirmąją vertę. Tokie baitų kodai yra apibrėžti kai kuriems dažniausiai stumiamiems įvairių tipų skaičiams. Šios instrukcijos užima tik 1 baitą baitų kodų sraute. Jie padidina baitkodo vykdymo efektyvumą ir sumažina baitekodo srautų dydį. Opcodes, stumiantys skruzdes ir plūduriuojančius, rodomi šioje lentelėje:
„Opcode“ | Operandas (-ai) | apibūdinimas |
---|---|---|
iconst_m1 | (nė vienas) | stumia int -1 ant kamino |
iconst_0 | (nė vienas) | stumia int 0 ant kamino |
iconst_1 | (nė vienas) | stumia int 1 ant kamino |
iconst_2 | (nė vienas) | stumia int 2 ant kamino |
iconst_3 | (nė vienas) | stumia int 3 ant kamino |
iconst_4 | (nė vienas) | stumia int 4 ant kamino |
iconst_5 | (nė vienas) | stumia int 5 ant kamino |
fconst_0 | (nė vienas) | pastumia plūdę 0 ant kamino |
fconst_1 | (nė vienas) | stumia plūdę 1 ant kamino |
fconst_2 | (nė vienas) | stumia plūdę 2 ant kamino |
Ankstesnėje lentelėje pateikti opcodai išstumia ir plūduriuoja, o tai yra 32 bitų vertės. Kiekvienas „Java“ kamino lizdas yra 32 bitų pločio. Todėl kiekvieną kartą, kai int arba plūdė stumiama ant kamino, ji užima vieną lizdą.
Kitoje lentelėje pateikti opcodai verčia ilgai ir dvigubai. Ilgos ir dvigubos vertės užima 64 bitus. Kiekvieną kartą, kai ant kamino stumiamas ilgas arba dvigubas, jo vertė užima du laiko tarpsnius. Opcodes, nurodantys konkrečią ilgą arba dvigubą vertę, kurią reikia stumti, rodomi šioje lentelėje:
„Opcode“ | Operandas (-ai) | apibūdinimas |
---|---|---|
lconst_0 | (nė vienas) | stumia ilgą 0 ant kamino |
lconst_1 | (nė vienas) | stumia ilgą 1 ant kamino |
dconst_0 | (nė vienas) | stumia dvigubą 0 ant kamino |
dconst_1 | (nė vienas) | stumia dvigubą 1 ant kamino |
Vienas kitas opcode išstumia numanomą pastovią vertę į kaminą. aconst_null
„Opcode“, parodytas šioje lentelėje, nustumia objekto nuorodą į kaminą. Objekto nuorodos formatas priklauso nuo JVM įgyvendinimo. Objekto nuoroda kažkaip nurodys „Java“ objektą ant šiukšlių surinktame krūvoje. Nulio objekto nuoroda rodo, kad objekto nuorodos kintamasis šiuo metu nėra susijęs su jokiu galiojančiu objektu. aconst_null
opcode naudojamas priskiriant nulį objekto nuorodos kintamajam.
„Opcode“ | Operandas (-ai) | apibūdinimas |
---|---|---|
aconst_null | (nė vienas) | stumia nulinę objekto nuorodą į kaminą |
Du opkodai nurodo pastovią stumti operandą, kuris iškart seka opkodą. Šie tolimesnėje lentelėje parodyti opkodai naudojami sveikų skaičių konstantoms, esančioms galiojančiame baitų ar trumpų tipų diapazone, stumti. Baitas ar trumpas, einantis po operacinio kodo, prieš jį stumiant ant kamino, išplečiamas į int, nes kiekvienas „Java“ kamino lizdas yra 32 bitų pločio. Ant kamino išstumtų baitų ir šortų operacijos iš tikrųjų atliekamos pagal jų ekvivalentus.
„Opcode“ | Operandas (-ai) | apibūdinimas |
---|---|---|
dvipusis | baitas1 | išplečia baitą1 (baito tipą) iki int ir stumia jį į kaminą |
sipushas | baitas1, baitas2 | išplečia baitą1, baitą2 (trumpas tipas) iki int ir stumia juos ant kamino |
Trys opcodes iš pastovaus telkinio stumia konstantas. Visos su klase susietos konstantos, pvz., Galutinių kintamųjų vertės, saugomos pastoviame klasės telkinyje. Opcodes, kurie pastumia konstantas iš pastovaus telkinio, turi operandus, kurie nurodo, kurią konstantą stumti, nurodydami pastovų baseino indeksą. „Java“ virtuali mašina ieškos nurodytos indekso konstantos, nustatys konstantos tipą ir nustums ją į rietuvę.
Pastovus telkinio indeksas yra nepasirašyta reikšmė, kuri iškart seka opkodą baitų kodų sraute. Opcodes lcd1
ir lcd2
stumkite 32 bitų elementą ant kamino, pavyzdžiui, int arba float. Skirtumas tarp lcd1
ir lcd2
yra tai lcd1
gali nurodyti tik pastovias telkinio vietas nuo vieno iki 255, nes jo indeksas yra tik 1 baitas. (Nuolatinė baseino vieta nulis nenaudojama.) lcd2
turi 2 baitų indeksą, todėl jis gali nurodyti bet kurią pastovią baseino vietą. lcd2w
taip pat turi 2 baitų indeksą ir jis naudojamas nurodyti bet kokią pastovią baseino vietą, kurioje yra ilgas arba dvigubas, užimantys 64 bitus. Opcodes, kurie pastumia konstantas iš pastovaus telkinio, rodomi šioje lentelėje:
„Opcode“ | Operandas (-ai) | apibūdinimas |
---|---|---|
ldc1 | rodyklės baitas1 | nustumia 32 bitų „index_te1“ nurodytą „konstanta_poolo“ įrašą į kaminą |
ldc2 | indexbyte1, indexbyte2 | stumia 32-bitų konstanta_poolo įvestį, nurodytą indexbyte1, indexbyte2, į rietuvę |
ldc2w | indexbyte1, indexbyte2 | stumia 64-bitų konstanta_poolo įvestį, nurodytą indexbyte1, indexbyte2, į rietuvę |
Vietinių kintamųjų stūmimas į kaminą
Vietiniai kintamieji saugomi specialioje rietuvės rėmo dalyje. Kamino rėmas yra rietuvės dalis, naudojama pagal šiuo metu vykdomą metodą. Kiekvieną kamino rėmą sudaro trys sekcijos - vietiniai kintamieji, vykdymo aplinka ir operando kaminas. Vietinio kintamojo stūmimas į kaminą iš tikrųjų reiškia vertės perkėlimą iš kamino rėmo vietinių kintamųjų sekcijos į operando sekciją. Šiuo metu vykdomo metodo operando skyrius visada yra kamino viršus, todėl vertės perkėlimas į dabartinio kamino rėmo operando skyrių yra tas pats, kas vertės perkėlimas į kamino viršų.
„Java“ kaminas yra paskutinis į pirmąjį 32 bitų laiko tarpsnių kaupimas. Kadangi kiekvienas kamino lizdas užima 32 bitus, visi vietiniai kintamieji užima mažiausiai 32 bitus. Ilgi ir dvigubi vietiniai kintamieji, kurie yra 64 bitų dydžiai, užima du laiko tarpsnius. Vietiniai kintamieji, kurių tipas arba trumpas tipas, saugomi kaip vietiniai kintamieji, kurių tipas yra int, tačiau jų vertė tinka mažesniam tipui. Pavyzdžiui, int vietiniame kintamajame, kuris rodo baito tipą, visada bus baitui galiojanti vertė (-128 <= reikšmė <= 127).
Kiekvienas metodo kintamasis turi unikalų indeksą. Metodo kamino rėmo vietinis kintamasis skyrius gali būti suprantamas kaip 32 bitų laiko tarpsnių masyvas, kiekvienas iš jų adresuojamas masyvo indeksu. Ilgi arba dvigubi vietiniai kintamieji, užimantys du laiko tarpsnius, nurodomi apatine iš dviejų lizdo indeksų. Pavyzdžiui, dvigubas, užimantis antrąsias ir tris vietas, būtų nurodomas dviejų indeksu.
Yra keli opkodai, kurie stumia int ir plukdo vietinius kintamuosius ant operando kamino. Kai kurie opkodai yra apibrėžti, kurie netiesiogiai nurodo dažniausiai naudojamą vietinio kintamojo padėtį. Pavyzdžiui, iload_0
įkelia int vietinį kintamąjį nulinėje padėtyje. Kiti vietiniai kintamieji stumiami į rietuvę opkodu, kuris ima vietinio kintamojo indeksą nuo pirmo baito, einančio po opkodo. iload
instrukcija yra tokio tipo opcode pavyzdys. Pirmasis baitas iload
yra interpretuojamas kaip nepasirašytas 8 bitų indeksas, nurodantis vietinį kintamąjį.
Nepasirašyti 8 bitų vietinių kintamųjų indeksai, pvz., Po to sekantys iload
nurodymą, metodo vietinių kintamųjų skaičių apribokite iki 256. Atskira instrukcija, vadinama platus
, gali išplėsti 8 bitų indeksą dar 8 bitais. Tai padidina vietinio kintamojo ribą iki 64 kilobaitų. platus
po „opcode“ yra 8 bitų operandas. platus
opcode ir jo operandas gali būti prieš nurodymą, pvz iload
, tam reikia 8 bitų nepasirašyto vietinio kintamojo indekso. JVM sujungia 8 bitų operandą platus
instrukcija su 8 bitų operandu iload
nurodymas sukurti 16 bitų nepasirašytą vietinio kintamojo indeksą.
Opcodes, kurie stumia int ir plūduriuoja vietinius kintamuosius ant rietuvės, rodomi šioje lentelėje:
„Opcode“ | Operandas (-ai) | apibūdinimas |
---|---|---|
iload | vindex | stumia int iš vietinės kintamos padėties vindex |
iload_0 | (nė vienas) | stumia int iš vietinės kintamos padėties nulio |
iload_1 | (nė vienas) | stumia int iš vietinės kintamos padėties viena |
iload_2 | (nė vienas) | stumia int iš vietinės kintamos padėties du |
iload_3 | (nė vienas) | stumia int iš vietinės kintamosios padėties trys |
apkrova | vindex | stumia plūdę iš vietinės kintamos padėties vindex |
fload_0 | (nė vienas) | stumia plūdę iš vietinės kintamos padėties nulio |
fload_1 | (nė vienas) | stumia plūdę iš vietinės kintamos padėties |
fload_2 | (nė vienas) | stumia plūdę iš vietinės kintamosios padėties |
fload_3 | (nė vienas) | stumia plūdę iš vietinės kintamosios padėties trys |
Kitoje lentelėje pateikiamos instrukcijos, kurios stumia vietinius kintamuosius, kurių tipas ilgas ir dvigubas, ant kamino. Šiomis instrukcijomis 64 bitai perkeliami iš kamino rėmo vietinio kintamojo skyriaus į operando skyrių.