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:
Tip | Definicija |
---|---|
bajt | enobajtno podpisano dve komplementarno celo število |
kratek | dvobajtno podpisano dve komplementarno celo število |
int | 4-bajtno podpisano dve komplementarno celo število |
dolga | 8-bajtno podpisano dve komplementarno celo število |
plovec | 4-bajtni IEEE 754 enonatančni plovec |
dvojno | 8-bajtni dvotočni plovec IEEE 754 |
char | 2-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:
Opcode | Operand (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:
Opcode | Operand (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.
Opcode | Operand (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.
Opcode | Operand (i) | Opis |
---|---|---|
bipush | bajt1 | razširi bajt1 (bajtni tip) na int in ga potisne v sklad |
sipush | bajt1, bajt2 | razš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:
Opcode | Operand (i) | Opis |
---|---|---|
ldc1 | indexbyte1 | potisne 32-bitni vnos constant_pool, ki ga določi indexbyte1, v sklad |
ldc2 | indexbyte1, indexbyte2 | potisne 32-bitni vnos constant_pool, določen z indexbyte1, indexbyte2, v sklad |
ldc2w | indexbyte1, indexbyte2 | potisne 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:
Opcode | Operand (i) | Opis |
---|---|---|
iload | vindex | potisne 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 |
fload | vindex | potisne 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.