Laipni lūdzam citā "Under The Hood" daļā. Šī sleja ļauj Java izstrādātājiem ieskatu, kas notiek zem viņu darbojošajām Java programmām. Šī mēneša rakstā sākotnēji aplūkots Java virtuālās mašīnas (JVM) baitu kodu instrukciju kopums. Raksts aptver primitīvos tipus, kurus darbina baitkodi, baitkodi, kas pārveido starp tipiem, un baitkodi, kas darbojas ar skursteni. Turpmākajos rakstos tiks apspriesti citi baitkodu saimes pārstāvji.
Baitkoda formāts
Bite kodi ir Java virtuālās mašīnas mašīnvaloda. Kad JVM ielādē klases failu, tā saņem vienu baitkodu straumi katrai klases metodei. Baitkodu plūsmas tiek glabātas JVM metodes apgabalā. Metodes baitkodi tiek izpildīti, kad šī metode tiek izsaukta programmas palaišanas laikā. Tos var izpildīt, veicot intepretāciju, apkopojot tieši laikā vai izmantojot jebkuru citu tehniku, kuru izvēlējies konkrēta JVM dizainers.
Metodes baitkoda straume ir Java virtuālās mašīnas instrukciju secība. Katra instrukcija sastāv no viena baita opkods kam seko nulle vai vairāk operandi. Opkods norāda veicamo darbību. Ja pirms JVM var veikt darbību, ir nepieciešama vairāk informācijas, šī informācija tiek kodēta vienā vai vairākos operandos, kas tūlīt seko opkodam.
Katram opcode veidam ir mnemonisks. Tipiskajā asamblejas valodas stilā Java baitkodu straumes var attēlot ar to mnemotiku, kam seko jebkuras operanda vērtības. Piemēram, šādu baitkodu plūsmu var izjaukt mnemos:
Bite koda straume: 03 3b 84 00 01 1a 05 68 3b a7 ff f9 // Demontāž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
Baitkoda instrukciju kopa tika veidota tā, lai tā būtu kompakta. Visas instrukcijas, izņemot divas, kas attiecas uz lēcienu galdā, ir saskaņotas ar baitu robežām. Kopējais opkodu skaits ir pietiekami mazs, lai opkodi aizņemtu tikai vienu baitu. Tas palīdz samazināt klases failu lielumu, kas, iespējams, pārvietojas pa tīkliem, pirms JVM ielādē. Tas arī palīdz mazināt JVM ieviešanas apjomu.
Visa aprēķināšana JVM centrā ir kaudze. Tā kā JVM nav reģistru nejaušu vērtību glabāšanai, viss ir jānovieto uz kaudzes, pirms to var izmantot aprēķinos. Tāpēc Bytecode instrukcijas galvenokārt darbojas ar skursteni. Piemēram, iepriekšējā baitkoda secībā vietējais mainīgais tiek reizināts ar diviem, vispirms nospiežot vietējo mainīgo uz kaudzes ar iload_0
instrukciju, pēc tam nospiežot divus uz kaudzes ar iconst_2
. Pēc tam, kad abi veseli skaitļi ir iestumti uz kaudzes, imul
instrukcija efektīvi atmet divus veselos skaitļus no kaudzes, reizina tos un izspiež rezultātu atpakaļ uz kaudzes. Rezultāts tiek izvadīts no kaudzes augšdaļas un tiek saglabāts atpakaļ vietējā mainīgajā istore_0
instrukcija. JVM tika veidots kā mašīna, kas balstīta uz kaudzēm, nevis uz reģistru, lai atvieglotu efektīvu ieviešanu reģistros nabadzīgajās arhitektūrās, piemēram, Intel 486.
Primitīvie veidi
JVM atbalsta septiņus primitīvus datu tipus. Java programmētāji var deklarēt un izmantot šo datu tipu mainīgos, un Java baitkodi darbojas ar šiem datu tipiem. Septiņi primitīvie veidi ir uzskaitīti šajā tabulā:
Tips | Definīcija |
---|---|
baits | viens baits parakstīja divu papildinājumu veselu skaitli |
īss | divbaiti parakstīja divu papildinājumu veselu skaitli |
int | 4 baiti parakstīja divu papildinājumu veselu skaitli |
ilgi | 8 baiti parakstīja divu papildinājumu veselu skaitli |
peldēt | 4 baitu IEEE 754 vienas precizitātes pludiņš |
dubultā | 8 baitu IEEE 754 dubultprecizitātes pludiņš |
char | 2 baitu neparakstīts Unicode raksturs |
Primitīvie tipi parādās kā operandi baitkodu plūsmās. Visi primitīvie tipi, kas aizņem vairāk nekā 1 baitu, baitkodu straumē tiek glabāti lielā endiāna secībā, kas nozīmē, ka augstākas kārtas baiti ir pirms zemākas kārtas baitiem. Piemēram, lai pastumtu konstantu vērtību 256 (heks. 0100) uz kaudzes, jūs izmantojat sipush
opkods, kam seko īss operands. Īsā baitkodu straumē, kas parādīta zemāk, tiek parādīts kā "01 00", jo JVM ir liels endijs. Ja JVM būtu mazs endijs, saīsinājums parādīsies kā "00 01".
Bite koda plūsma: 17 01 00 // Demontāža: sipush 256; 17 01 00
Java opkodi parasti norāda to operandu veidu. Tas ļauj operandiem būt tikai pašiem, bez vajadzības identificēt viņu tipu JVM. Piemēram, tā vietā, lai būtu viens opkods, kas vietējo mainīgo iespiež kaudzē, JVM ir vairāki. Opcodes iload
, krava
, slodze
, un ielādēt
uz kaudzes nospiediet attiecīgi int, long, float un double lokālos mainīgos.
Konstantu spiešana uz kaudzes
Daudzi opkodi uzstāda konstantes uz kaudzes. Opcodes norāda nemainīgo vērtību, kas jāstumj trīs dažādos veidos. Pastāvīgā vērtība ir vai nu netieša pašā opkodā, tā seko opkodam baitu koda straumē kā operands vai tiek ņemta no nemainīgā kopas.
Daži opkodi paši par sevi norāda virzienu un nemainīgu vērtību. Piemēram, iconst_1
opcode liek JVM virzīt veselu skaitli vienu vērtību. Šādi baitkodi ir definēti dažiem dažāda veida bieži virzītiem skaitļiem. Šīs instrukcijas aizņem tikai 1 baitu baitu kodu straumē. Tie palielina bytecode izpildes efektivitāti un samazina bytecode plūsmu lielumu. Opcodes, kas piespiež ints un pludina, ir parādīti šajā tabulā:
Opcode | Operands (-i) | Apraksts |
---|---|---|
iconst_m1 | (nav) | nospiež int -1 uz kaudzes |
iconst_0 | (nav) | nospiež int 0 uz kaudzes |
iconst_1 | (nav) | nospiež int 1 uz kaudzes |
iconst_2 | (nav) | nospiež int 2 uz kaudzes |
iconst_3 | (nav) | nospiež int 3 uz kaudzes |
ikona_4 | (nav) | nospiež int 4 uz kaudzes |
iconst_5 | (nav) | nospiež int 5 uz kaudzes |
fconst_0 | (nav) | nospiež pludiņu 0 uz kaudzes |
fconst_1 | (nav) | nospiež pludiņu 1 uz kaudzes |
fconst_2 | (nav) | nospiež pludiņu 2 uz kaudzes |
Iepriekšējā tabulā redzamie opkodi nospiež ints un peld, kas ir 32 bitu vērtības. Katrs Java kaudzes slots ir 32 bitu plats. Tāpēc katru reizi, kad int vai pludiņš tiek stumts uz kaudzes, tas aizņem vienu slotu.
Nākamajā tabulā redzamie opkodi spiež garus un dubultus. Garās un dubultās vērtības aizņem 64 bitus. Katru reizi, kad garu vai dubultu tiek nospiests uz kaudzes, tā vērtība aizņem divas vietas uz kaudzes. Opcodes, kas norāda noteiktu garu vai dubultu virzāmo vērtību, ir redzamas šajā tabulā:
Opcode | Operands (-i) | Apraksts |
---|---|---|
lconst_0 | (nav) | nospiež garo 0 uz kaudzes |
lconst_1 | (nav) | nospiež garo 1 uz kaudzes |
dconst_0 | (nav) | nospiež dubulto 0 uz kaudzes |
dconst_1 | (nav) | nospiež dubultu 1 uz kaudzes |
Viens cits opkods nospiež netiešo nemainīgo vērtību uz kaudzes. The aconst_null
opkods, kas parādīts nākamajā tabulā, uz kaudzīti nospiež nulles objekta atsauci. Objekta atsauces formāts ir atkarīgs no JVM ieviešanas. Objekta atsauce kaut kādā veidā atsauksies uz Java objektu atkritumu savāktajā kaudzē. Nulles objekta atsauce norāda, ka objekta atsauces mainīgais pašlaik neattiecas uz nevienu derīgu objektu. The aconst_null
opcode tiek izmantots nulles piešķiršanas procesā objekta atsauces mainīgajam.
Opcode | Operands (-i) | Apraksts |
---|---|---|
aconst_null | (nav) | nospiež nulles objekta atsauci uz kaudzīti |
Divi opkodi norāda pastāvīgo spiedienu ar operandu, kas tūlīt seko opkodam. Šie opcodes, kas parādīti nākamajā tabulā, tiek izmantoti, lai virzītu veselu skaitļu konstantes, kas ir derīgā diapazona baitu vai īsu tipu diapazonā. Baits vai īss, kas seko opkodam, tiek paplašināts līdz int, pirms tas tiek stumts uz kaudzes, jo katrs Java kaudzes slots ir 32 bitu plats. Operācijas ar baitiem un šortiem, kas ir iestumti uz kaudzes, faktiski tiek veiktas pēc to ekvivalentiem.
Opcode | Operands (-i) | Apraksts |
---|---|---|
bipush | baits1 | paplašina 1. baitu (baita veidu) līdz int un nospiež to uz kaudzes |
sipush | baits1, baits2 | izpleš baitu1, baitu2 (īsu tipu) līdz int un nospiež tos uz kaudzes |
Trīs opkodi izstumj konstantes no nemainīgā baseina. Visas ar klasi saistītās konstantes, piemēram, galīgo mainīgo vērtības, tiek saglabātas klases nemainīgajā krājumā. Opkodos, kas izspiež konstantes no nemainīgā kopas, ir operandi, kas norāda, kuru konstanti virzīt, norādot nemainīgu kopas indeksu. Java virtuālā mašīna meklēs konstanti, kas dota indeksam, noteiks konstantes tipu un nospiedīs to uz kaudzes.
Pastāvīgā kopas indekss ir neparakstīta vērtība, kas tūlīt seko opkodam baitu kodu straumē. Opcodes lcd1
un lcd2
nospiediet uz kaudzes 32 bitu priekšmetu, piemēram, int vai float. Atšķirība starp lcd1
un lcd2
vai tas ir lcd1
var atsaukties tikai uz nemainīgām baseina vietām no viena līdz 255, jo tā indekss ir tikai 1 baits. (Pastāvīga baseina atrašanās vieta nulle nav izmantota.) lcd2
ir 2 baitu indekss, tāpēc tas var atsaukties uz jebkuru nemainīgu baseina atrašanās vietu. lcd2w
ir arī 2 baitu indekss, un to izmanto, lai atsauktos uz jebkuru nemainīgu baseina vietu, kurā ir garš vai dubults, kas aizņem 64 bitus. Opkodi, kas izspiež konstantes no nemainīgā kopas, ir parādīti šajā tabulā:
Opcode | Operands (-i) | Apraksts |
---|---|---|
ldc1 | rādītājbaits1 | izspiež kaudzē 32 bitu konstante_pool ievadi, ko norādījis indexbyte1 |
ldc2 | indexbyte1, indexbyte2 | izspiež kaudzē 32 bitu konstante_pool ievadi, ko norādījuši indexbyte1, indexbyte2 |
ldc2w | indexbyte1, indexbyte2 | izspiež kaudzē 64 bitu konstante_pool ievadi, ko norādījuši indexbyte1, indexbyte2 |
Vietējo mainīgo spiešana uz kaudzes
Vietējie mainīgie tiek glabāti īpašā kaudzes rāmja sadaļā. Steka rāmis ir tā kaudzes daļa, kuru izmanto pašreiz izpildītā metode. Katru kaudzes rāmi veido trīs sadaļas - lokālie mainīgie, izpildes vide un operanda kaudze. Vietējā mainīgā virzīšana uz kaudzīti faktiski nozīmē vērtības pārvietošanu no kaudzes rāmja vietējo mainīgo sadaļas uz operanda sadaļu. Pašlaik izpildītās metodes operanda sadaļa vienmēr ir kaudzes augšdaļa, tāpēc vērtības nospiešana uz pašreizējā kaudzes rāmja operanda sadaļu ir tāda pati kā vērtības nospiešana kaudzes augšdaļā.
Java kaudze ir pēdējā-pirmā, pirmā-ārējā 32 bitu slotu kaudze. Tā kā katrs kaudzes slots aizņem 32 bitus, visi lokālie mainīgie aizņem vismaz 32 bitus. Garie un dubultie lokālie mainīgie, kas ir 64 bitu lielumi, aizņem divus slotus uz kaudzes. Vietējie mainīgie ar baitu vai īsu tipu tiek saglabāti kā int tipa lokālie mainīgie, bet ar vērtību, kas ir derīga mazākajam tipam. Piemēram, int lokālajā mainīgajā, kas attēlo baita tipu, vienmēr būs baitam derīga vērtība (-128 <= vērtība <= 127).
Katram metodes lokālajam mainīgajam ir unikāls indekss. Metodes kaudzes rāmja lokālo mainīgo sadaļu var uzskatīt par 32 bitu slotu masīvu, no kuriem katru adresē masīva indekss. Uz garajiem vai dubultajiem lokālajiem mainīgajiem lielumiem, kas aizņem divus laika nišus, atsaucas zemākais no diviem laika nišu indeksiem. Piemēram, uz dubultnieku, kas aizņem otro un trešo vietu, atsaucas indekss divi.
Pastāv vairāki opkodi, kas vietējos mainīgos izstumj un uzpeld operanda kaudzē. Ir definēti daži opkodi, kas netieši norāda uz parasti izmantoto lokālo mainīgo pozīciju. Piemēram, iload_0
ielādē int lokālo mainīgo nulles pozīcijā. Citus lokālos mainīgos uz steku iestumj opkods, kas vietējā mainīgā indeksu ņem no pirmā baita, kas seko opkodam. The iload
instrukcija ir šāda veida opcode piemērs. Pirmais baits, kas seko iload
tiek interpretēts kā neparakstīts 8 bitu indekss, kas attiecas uz vietējo mainīgo.
Neparakstīti 8 bitu lokālo mainīgo indeksi, piemēram, tas, kas seko iload
instrukciju, ierobežojiet lokālo mainīgo skaitu metodē līdz 256. Atsevišķa instrukcija, ko sauc plašs
, var pagarināt 8 bitu indeksu vēl par 8 bitiem. Tas vietējo mainīgo robežu paaugstina līdz 64 kilobaitiem. The plašs
opkodam seko 8 bitu operands. The plašs
opkods un tā operands var būt pirms instrukcijas, piemēram, iload
, kas prasa 8 bitu neparakstītu vietējo mainīgo indeksu. JVM apvieno operētājsistēmas 8 bitu operandu plašs
instrukcija ar 8 bitu operandu iload
instrukcija, lai iegūtu 16 bitu neparakstītu lokālo mainīgo indeksu.
Opcodes, kas izspiež int un peld lokālos mainīgos uz kaudzes, ir parādīti šajā tabulā:
Opcode | Operands (-i) | Apraksts |
---|---|---|
iload | vindex | nospiež int no lokālās mainīgās pozīcijas vindex |
iload_0 | (nav) | nospiež int no vietējā mainīgā stāvokļa nulle |
iload_1 | (nav) | nospiež int no lokālā mainīgā stāvokļa viens |
iload_2 | (nav) | nospiež int no vietējā mainīgā stāvokļa divi |
iload_3 | (nav) | nospiež int no vietējā mainīgā stāvokļa trīs |
slodze | vindex | nospiež pludiņu no vietējā mainīgā stāvokļa vindex |
fload_0 | (nav) | nospiež pludiņu no nulles lokālā mainīgā stāvokļa |
fload_1 | (nav) | spiež pludiņu no lokālā mainīgā stāvokļa |
fload_2 | (nav) | nospiež pludiņu no vietējā mainīgā stāvokļa diviem |
fload_3 | (nav) | nospiež pludiņu no vietējā mainīgā stāvokļa trīs |
Nākamajā tabulā ir parādītas instrukcijas, ar kurām vietējie mainīgie garā un dubultā veidā tiek virzīti uz kaudzīti. Šīs instrukcijas pārvieto 64 bitus no kaudzes rāmja lokālās mainīgās daļas uz operanda sadaļu.