Programavimas

„Bytecode“ pagrindai

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:

TipasApibrėžimas
baitasvieno baito pasirašytas dviejų papildomas skaičius
trumpasdvibaitis pasirašė dviejų komplemento skaičių
tarpt4 baitai pasirašė dviejų papildų skaičių
ilgas8 baitai pasirašė dviejų papildų skaičių
plūdė4 baitų „IEEE 754“ vieno tikslumo plūdė
dvigubai8 baitų IEEE 754 dvigubo tikslumo plūdė
char2 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, apkrovair 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
dvipusisbaitas1išplečia baitą1 (baito tipą) iki int ir stumia jį į kaminą
sipushasbaitas1, baitas2iš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
ldc1rodyklės baitas1nustumia 32 bitų „index_te1“ nurodytą „konstanta_poolo“ įrašą į kaminą
ldc2indexbyte1, indexbyte2stumia 32-bitų konstanta_poolo įvestį, nurodytą indexbyte1, indexbyte2, į rietuvę
ldc2windexbyte1, indexbyte2stumia 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
iloadvindexstumia 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
apkrovavindexstumia 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ų.