Programiranje

Osnove bytecode

Dobrodošli v drugem delu "Under the Hood." Ta stolpec daje razvijalcem Java vpogled v dogajanje pod njihovimi programi Java. V tem mesečnem članku je uvodno predstavljen nabor ukazov bajt kod navideznega stroja Java (JVM). Članek zajema primitivne tipe, ki jih obdelujejo bajtke, bajtkode, ki pretvarjajo med vrstami, in bajtkode, ki delujejo na skladu. V nadaljnjih člankih bomo razpravljali o drugih članih družine bajt kod.

Oblika bytecode

Bytecode so strojni jezik navideznega stroja Java. Ko JVM naloži datoteko razreda, dobi en tok bajt kod za vsako metodo v razredu. Tokovi bajtkod so shranjeni v območju metode JVM. Bytecode za metodo se izvede, ko se ta metoda prikliče med izvajanjem programa. Izvedejo jih lahko z interpretacijo, pravočasnim prevajanjem ali katero koli drugo tehniko, ki jo je izbral oblikovalec določenega JVM.

Tok byte-kode metode je zaporedje navodil za navidezni stroj Java. Vsako navodilo je sestavljeno iz enobajta opcode čemur sledi nič ali več operandi. Optična koda označuje dejanje. Če je potrebno več informacij, preden lahko JVM izvede dejanje, se te informacije kodirajo v enega ali več operandov, ki takoj sledijo kodi operacijskega sistema.

Vsaka vrsta opcode ima mnemoteko. V tipičnem slogu montažnega jezika lahko tokove bajtkodov Java predstavljajo svoje mnemotehnike, ki jim sledijo katere koli vrednosti operanda. Naslednji tok bajtkod je na primer mogoče razstaviti v mnemotehnike:

// Tok bytecode: 03 3b 84 00 01 1a 05 68 3b a7 ff f9 // Demontaža: 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 

Nabor navodil bajtkode je bil zasnovan tako, da je kompakten. Vsa navodila, razen dveh, ki obravnavata preskakovanje tabel, so poravnana na mejah bajtov. Skupno število opcode je dovolj majhno, da zavzamejo samo en bajt. To pomaga zmanjšati velikost datotek razreda, ki morda potujejo po omrežjih, preden jih naloži JVM. Pomaga tudi pri ohranjanju majhnosti izvedbe JVM.

Vsi izračuni v JVM se osredotočijo na sklad. Ker JVM nima registrov za shranjevanje dodatnih vrednosti, je treba vse skupaj potisniti na sklad, preden ga lahko uporabimo pri izračunu. Navodila za bytecode torej delujejo predvsem na skladu. Na primer, v zgornjem zaporedju bajt kod se lokalna spremenljivka pomnoži z dve, tako da lokalno spremenljivko najprej potisnete v sklad s iload_0 navodilo, nato potisnite dva na sklad s iconst_2. Ko sta obe celo število potisnjeni v sklad, se imul navodilo učinkovito izvleče dve celi števili iz sklada, jih pomnoži in rezultat potisne nazaj v sklad. Rezultat se pokaže z vrha sklada in shrani nazaj v lokalno spremenljivko istore_0 navodila. JVM je bil zasnovan kot stroj na osnovi skladov in ne kot stroj, ki temelji na registru, da bi olajšal učinkovito izvajanje na arhitekturah, ki so slabe z registra, kot je Intel 486.

Primitivni tipi

JVM podpira sedem primitivnih podatkovnih tipov. Programerji Java lahko deklarirajo in uporabljajo spremenljivke teh podatkovnih tipov, bajtode Java pa delujejo na te tipe podatkov. V naslednji tabeli je naštetih sedem primitivnih vrst:

TipDefinicija
bajtenobajtno podpisano dve komplementarno celo število
kratekdvobajtno podpisano dve komplementarno celo število
int4-bajtno podpisano dve komplementarno celo število
dolga8-bajtno podpisano dve komplementarno celo število
plovec4-bajtni IEEE 754 enonatančni plovec
dvojno8-bajtni dvotočni plovec IEEE 754
char2-bajtni nepodpisani znak Unicode

Primitivni tipi so v tokovih bajt kod prikazani kot operandi. Vsi primitivni tipi, ki zasedajo več kot 1 bajt, so shranjeni v velikem endian vrstnem redu v toku bajt kod, kar pomeni, da bajti višjega reda pred bajti nižjega reda. Če želite na primer potisniti konstantno vrednost 256 (šestnajstiška 0100) na sklad, uporabite sipush opcode, ki ji sledi kratek operand. Kratka beseda se v toku bajt kod, prikazanem spodaj, prikaže kot "01 00", ker je JVM velik endian. Če bi bil JVM malo endian, bi bil kratek prikazan kot "00 01".

 // Tok bytecode: 17 01 00 // Demontaža: sipush 256; // 17 01 00 

Java kode opcij običajno označujejo vrsto njihovih operandov. To omogoča, da so operandi samo oni sami, JVM pa ni treba določiti njihove vrste. Na primer, namesto da ima eno kodo opcij, ki potisne lokalno spremenljivko v sklad, jih ima JVM več. Opcode iload, Naloži, fload, in dload potisnite lokalne spremenljivke tipa int, long, float oziroma double na sklad.

Potiskanje konstant na sklad

Številne opcode potisnejo konstante na sklad. Opcode označujejo konstantno vrednost za potiskanje na tri različne načine. Vrednost konstante je bodisi implicitna v sami opcode, bodisi ji sledi v toku bytecode kot operand ali pa je vzeta iz področja konstant.

Nekatere opcode same označujejo vrsto in konstantno vrednost za potiskanje. Na primer iconst_1 opcode sporoči JVM, da potisne celoštevilčno vrednost ena. Takšne bytecode so definirane za nekatere pogosto potisnjene številke različnih vrst. Ta navodila zajemajo samo 1 bajt v toku bajt kod. Povečujejo učinkovitost izvajanja bajtkode in zmanjšujejo velikost tokov bajtkode. Opcode, ki potiskajo inte in floats, so prikazani v naslednji tabeli:

OpcodeOperand (i)Opis
iconst_m1(nič)potisne int -1 na sklad
iconst_0(nič)potisne int 0 na sklad
iconst_1(nič)potisne int 1 na sklad
iconst_2(nič)potisne int 2 na sklad
iconst_3(nič)potisne int 3 na sklad
iconst_4(nič)potisne int 4 na sklad
iconst_5(nič)potisne int 5 na sklad
fconst_0(nič)potisne float 0 na sklad
fconst_1(nič)potisne float 1 na sklad
fconst_2(nič)potisne float 2 na sklad

Opcode, prikazani v prejšnji tabeli, potiskajo ints in floats, ki sta 32-bitni vrednosti. Vsaka reža na kupu Java je široka 32 bitov. Zato vsakič, ko int ali float potisnemo na sklad, zasede eno režo.

Opcode, prikazani v naslednji tabeli, potisnejo dolžine in podvojitve. Dolge in dvojne vrednosti zasedajo 64 bitov. Vsakič, ko se long ali double potisne na sklad, njegova vrednost zasede dve reži na kupu. Opcode, ki označujejo določeno dolgo ali dvojno vrednost za potiskanje, so prikazani v naslednji tabeli:

OpcodeOperand (i)Opis
lconst_0(nič)potisne dolg 0 na sklad
lconst_1(nič)potisne dolg 1 na sklad
dconst_0(nič)potisne dvojno 0 na sklad
dconst_1(nič)potisne dvojno 1 na sklad

Ena druga opcode potisne implicitno konstantno vrednost na sklad. The aconst_null opcode, prikazana v naslednji tabeli, potisne sklic ničelnega predmeta na sklad. Oblika sklica na objekt je odvisna od izvedbe JVM. Sklic predmeta se bo nekako skliceval na objekt Java na kopici zbranih smeti. Ničelna referenca predmeta pomeni, da se referenčna spremenljivka predmeta trenutno ne nanaša na noben veljaven objekt. The aconst_null opcode se uporablja v postopku dodelitve ničelne vrednosti referenčni spremenljivki objekta.

OpcodeOperand (i)Opis
aconst_null(nič)potisne sklic ničelnega predmeta na sklad

Dve opcode označujeta konstanto za potiskanje z operandom, ki takoj sledi opcode. Te kode opcij, prikazane v naslednji tabeli, se uporabljajo za potiskanje celoštevilčnih konstant, ki so znotraj veljavnega obsega za bajtne ali kratke vrste. Bajt ali okrajšava, ki sledi optični kodi, se razširi na int, preden jo potisne v sklad, ker je vsa reža na kupu Java široka 32 bitov. Operacije na bajtih in kratkih hlačah, ki so bile potisnjene na sklad, se dejansko izvajajo na njihovih int ekvivalentih.

OpcodeOperand (i)Opis
bipushbajt1razširi bajt1 (bajtni tip) na int in ga potisne v sklad
sipushbajt1, bajt2razširi bajt1, bajt2 (kratek tip) na int in ga potisne v sklad

Tri konstante iz konstantnega bazena potisnejo konstante. Vse konstante, povezane z razredom, na primer vrednosti končnih spremenljivk, so shranjene v konstantnem področju razreda. Opcode, ki potisnejo konstante iz konstantnega področja, imajo operande, ki z določitvijo indeksa konstantnega področja kažejo, katero konstanto naj potisnejo. Navidezni stroj Java bo poiskal konstanto glede na indeks, določil vrsto konstante in jo potisnil v sklad.

Indeks konstantnega področja je nepodpisana vrednost, ki takoj sledi opcode v toku bytecode. Opcode lcd1 in lcd2 potisnite 32-bitni element na sklad, na primer int ali float. Razlika med lcd1 in lcd2 je to lcd1 se lahko nanaša samo na stalne lokacije bazena od enega do 255, ker je njegov indeks le 1 bajt. (Stalna lokacija bazena nič ni uporabljena.) lcd2 ima 2-bajtni indeks, zato se lahko nanaša na katero koli stalno lokacijo bazena. lcd2w ima tudi 2-bajtni indeks in se uporablja za sklicevanje na katero koli konstantno lokacijo bazena, ki vsebuje long ali double, ki zaseda 64 bitov. Opcode, ki potisnejo konstante iz konstantnega področja, so prikazani v naslednji tabeli:

OpcodeOperand (i)Opis
ldc1indexbyte1potisne 32-bitni vnos constant_pool, ki ga določi indexbyte1, v sklad
ldc2indexbyte1, indexbyte2potisne 32-bitni vnos constant_pool, določen z indexbyte1, indexbyte2, v sklad
ldc2windexbyte1, indexbyte2potisne 64-bitni vnos constant_pool, določen z indexbyte1, indexbyte2, v sklad

Potiskanje lokalnih spremenljivk v sklad

Lokalne spremenljivke so shranjene v posebnem odseku okvira sklada. Okvir sklada je del sklada, ki ga uporablja trenutno izvajana metoda. Vsak okvir sklada je sestavljen iz treh odsekov - lokalnih spremenljivk, okolja izvajanja in sklada operandov. Potiskanje lokalne spremenljivke v sklad dejansko vključuje premikanje vrednosti iz odseka lokalnih spremenljivk v okviru sklada v odsek operanda. Operacijski odsek trenutno izvajane metode je vedno vrh sklada, zato je potiskanje vrednosti na odsek operanda trenutnega okvira sklada enako kot potiskanje vrednosti na vrh sklada.

Sklop Java je 32-bitni sloti zadnji vhod, prvi izhod. Ker vsaka reža v kupu zaseda 32 bitov, vse lokalne spremenljivke zasedajo vsaj 32 bitov. Lokalni spremenljivki tipa long in double, ki sta 64-bitni količini, zasedata dve reži na kupu. Lokalne spremenljivke bajta tipa ali kratke so shranjene kot lokalne spremenljivke tipa int, vendar z vrednostjo, ki velja za manjši tip. Na primer, lokalna spremenljivka int, ki predstavlja vrsto bajta, bo vedno vsebovala vrednost, ki velja za bajt (-128 <= vrednost <= 127).

Vsaka lokalna spremenljivka metode ima edinstven indeks. Lokalni spremenljivi odsek okvira sklada metode lahko predstavljamo kot niz 32-bitnih rež, od katerih je vsak naslovljiv z indeksom matrike. Lokalne spremenljivke tipa long ali double, ki zasedajo dve reži, se sklicujejo na spodnji od obeh indeksov rež. Na primer, dvojnik, ki zaseda reži dve in tri, bi bil označen z indeksom dva.

Obstaja več opcode, ki potisnejo lokalne in spremenljive lokalne spremenljivke na sklad operand. Določene so nekatere opcode, ki se implicitno nanašajo na pogosto uporabljen položaj lokalne spremenljivke. Na primer, iload_0 naloži lokalno spremenljivko int na položaju nič. Druge lokalne spremenljivke potisne na sklad z optično kodo, ki vzame indeks lokalne spremenljivke iz prvega bajta, ki sledi optični kodi. The iload navodilo je primer te vrste opcode. Sledi prvi bajt iload se razlaga kot nepodpisani 8-bitni indeks, ki se nanaša na lokalno spremenljivko.

Nepodpisani 8-bitni lokalni indeksi spremenljivk, na primer tisti, ki sledi iload navodilo, omejite število lokalnih spremenljivk v metodi na 256. Ločeno navodilo, imenovano široko, lahko podaljša 8-bitni indeks za dodatnih 8 bitov. S tem se lokalna spremenljivka dvigne na 64 kilobajtov. The široko opcode sledi 8-bitni operand. The široko opcode in njen operand lahko pred ukazom, kot je iload, ki ima 8-bitni nepodpisani indeks lokalne spremenljivke. JVM združuje 8-bitni operand široko navodilo z 8-bitnim operandom iload navodilo za pridobitev 16-bitnega nepodpisanega indeksa lokalne spremenljivke.

Opcode, ki potisnejo lokalne in spremenljive lokalne spremenljivke na sklad, so prikazani v naslednji tabeli:

OpcodeOperand (i)Opis
iloadvindexpotisne int iz lokalnega spremenljivega položaja vindex
iload_0(nič)potisne int iz položaja lokalne spremenljivke nič
iload_1(nič)potisne int iz lokalne spremenljivke položaj ena
iload_2(nič)potisne int iz lokalne spremenljivke položaj dva
iload_3(nič)potisne int iz položaja lokalne spremenljivke tri
floadvindexpotisne plovec iz lokalnega spremenljivega položaja vindex
fload_0(nič)potisne float iz lokalne spremenljive lege nič
fload_1(nič)potisne float iz lokalne spremenljivke položaj ena
fload_2(nič)potisne float iz lokalne spremenljivke položaj dva
fload_3(nič)potisne float iz lokalne spremenljivke položaj tri

Naslednja tabela prikazuje navodila, ki potisnejo lokalne spremenljivke tipa long in double na sklad. Ta navodila premaknejo 64 bitov iz odseka lokalne spremenljivke okvira sklada v odsek operanda.

$config[zx-auto] not found$config[zx-overlay] not found