Programmering

Grunnleggende om Bytecode

Velkommen til en annen del av "Under The Hood." Denne kolonnen gir Java-utviklere et glimt av hva som skjer under deres Java-programmer. Denne månedens artikkel tar en første titt på bytecode-instruksjonssettet til Java virtual machine (JVM). Artikkelen dekker primitive typer som drives av bytecodes, bytecodes som konverterer mellom typer, og bytecodes som fungerer på stacken. Senere artikler vil diskutere andre medlemmer av bytecode-familien.

Bytekodeformatet

Bytecodes er maskinspråket til den virtuelle Java-maskinen. Når en JVM laster inn en klassefil, får den en strøm med bytekoder for hver metode i klassen. Bytekodestrømmene lagres i metodeområdet til JVM. Bytekodene for en metode kjøres når den metoden påkalles i løpet av programmet. De kan utføres ved tolking, just-in-time kompilering eller hvilken som helst annen teknikk som ble valgt av designeren av en bestemt JVM.

En metodes bytecode-strøm er en rekke instruksjoner for den virtuelle Java-maskinen. Hver instruksjon består av en byte opcode etterfulgt av null eller mer operander. Opkoden indikerer handlingen som skal utføres. Hvis det kreves mer informasjon før JVM kan utføre handlingen, blir den informasjonen kodet i en eller flere operander som umiddelbart følger opkoden.

Hver type opcode har et minnesmerke. I den typiske monteringsspråklisten kan strømmer av Java-bykoder representeres av deres mnemonics etterfulgt av eventuelle operandverdier. For eksempel kan følgende strøm av bytekoder demonteres til mnemonics:

// Bytecode stream: 03 3b 84 00 01 1a 05 68 3b a7 ff f9 // Demontering: iconst_0 // 03 istore_0 // 3b iinc 0, 1 // 84 00 01 iload_0 // 1a iconst_2 // 05 imul // 68 istore_0 // 3b gå til -7 // a7 ff f9 

Instruksjonssettet for bytecode var designet for å være kompakt. Alle instruksjoner, unntatt to som omhandler bordhopping, er justert etter bytegrenser. Totalt antall opkoder er lite nok til at opkoder bare opptar en byte. Dette bidrar til å minimere størrelsen på klassefiler som kan reise på tvers av nettverk før de lastes inn av en JVM. Det hjelper også med å holde størrelsen på JVM-implementeringen liten.

All beregning i JVM sentrerer på bunken. Fordi JVM ikke har noen registre for lagring av abitrære verdier, må alt skyves på stabelen før den kan brukes i en beregning. Bytecode-instruksjoner fungerer derfor primært på bunken. For eksempel i den ovennevnte bytekodesekvensen multipliseres en lokal variabel med to ved først å skyve den lokale variabelen på stakken med iload_0 instruksjon, og skyver deretter to på bunken med iconst_2. Etter at begge heltallene er skjøvet på bunken, blir imul instruksjon spretter effektivt de to heltallene fra bunken, multipliserer dem og skyver resultatet tilbake på bunken. Resultatet poppes av toppen av bunken og lagres tilbake til den lokale variabelen av istore_0 instruksjon. JVM ble designet som en stabelbasert maskin i stedet for en registerbasert maskin for å lette effektiv implementering på registerfattige arkitekturer som Intel 486.

Primitive typer

JVM støtter syv primitive datatyper. Java-programmerere kan erklære og bruke variabler av disse datatypene, og Java-bykoder bruker disse datatypene. De syv primitive typene er oppført i følgende tabell:

TypeDefinisjon
byteen byte signerte tos komplement heltall
kortto-byte signerte tos komplement heltall
int4 byte signerte tos komplement heltall
lang8-byte signerte tos komplement heltall
flyte4-byte IEEE 754 flyter med en enkelt presisjon
dobbelt8-byte IEEE 754 dobbel presisjonsflyter
røye2-byte usignert Unicode-tegn

De primitive typene vises som operander i bytekodestrømmer. Alle primitive typer som opptar mer enn 1 byte lagres i stor endian rekkefølge i bytekodestrømmen, noe som betyr høyere orden byte foran lavere orden byte. For eksempel, for å skyve den konstante verdien 256 (hex 0100) på stakken, bruker du sipush opcode etterfulgt av en kort operand. Den korte vises i bytekodestrømmen, vist nedenfor, som "01 00" fordi JVM er stor-endian. Hvis JVM var liten endian, ville den korte vises som "00 01".

 // Bytecode stream: 17 01 00 // Demontering: sipush 256; // 17 01 00 

Java-opkoder angir vanligvis typen operander. Dette gjør at operander bare kan være seg selv, uten behov for å identifisere typen til JVM. For eksempel, i stedet for å ha en opcode som skyver en lokal variabel på bunken, har JVM flere. Opcodes iload, Jeg laster, flyte, og dload skyv lokale variabler av henholdsvis type int, long, float og double på bunken.

Skyv konstanter på stabelen

Mange opkoder skyver konstanter på bunken. Opkoder indikerer den konstante verdien som skal skyves på tre forskjellige måter. Den konstante verdien er enten implisitt i selve opkoden, følger opkoden i bytekodestrømmen som en operand, eller blir tatt fra den konstante puljen.

Noen opkoder indikerer i seg selv en type og konstant verdi å skyve. For eksempel iconst_1 opcode forteller JVM å skyve heltallverdi en. Slike bytekoder er definert for noen ofte pressede tall av forskjellige typer. Disse instruksjonene opptar bare 1 byte i bytekodestrømmen. De øker effektiviteten til utføring av bytecode og reduserer størrelsen på bytecode-strømmer. Opkodene som skyver inn og flyter vises i følgende tabell:

OpcodeOperand (er)Beskrivelse
iconst_m1(ingen)skyver int -1 på bunken
ikonst_0(ingen)skyver int 0 på bunken
iconst_1(ingen)skyver int 1 på bunken
iconst_2(ingen)skyver int 2 på bunken
iconst_3(ingen)skyver int 3 på bunken
ikonst_4(ingen)skyver int 4 på bunken
ikonst_5(ingen)skyver int 5 på bunken
fconst_0(ingen)skyver float 0 på stabelen
fconst_1(ingen)skyver flyter 1 på bunken
fconst_2(ingen)skyver flyter 2 på stabelen

Opkodene vist i forrige tabell skyver inn og flyter, som er 32-biters verdier. Hvert spor på Java-stakken er 32 bits bredt. Derfor opptar det ett spor hver gang en int eller flottør skyves på bunken.

Opkodene vist i neste tabell skyver lengter og dobler. Lange og doble verdier opptar 64 bits. Hver gang en lang eller dobbel skyves på bunken, har verdien to spor på bunken. Opkoder som indikerer en bestemt lang eller dobbel verdi å trykke, vises i følgende tabell:

OpcodeOperand (er)Beskrivelse
lconst_0(ingen)skyver langt 0 på bunken
lconst_1(ingen)skyver lang 1 på bunken
dconst_0(ingen)skyver dobbelt 0 på bunken
dconst_1(ingen)skyver dobbelt 1 på bunken

Én annen opcode skyver en implisitt konstant verdi på bunken. De aconst_null opcode, vist i tabellen nedenfor, skyver en null-objektreferanse på bunken. Formatet på en objektreferanse avhenger av JVM-implementeringen. En objektreferanse vil på en eller annen måte henvise til et Java-objekt på søppelet. En null-objektreferanse indikerer at en objektreferansevariabel for øyeblikket ikke refererer til noe gyldig objekt. De aconst_null opcode brukes i prosessen med å tilordne null til en objektreferansevariabel.

OpcodeOperand (er)Beskrivelse
aconst_null(ingen)skyver en null-objektreferanse på bunken

To opkoder indikerer konstanten å skyve med en operand som umiddelbart følger opkoden. Disse opkodene, vist i tabellen nedenfor, brukes til å skyve heltallskonstanter som er innenfor det gyldige området for byte- eller korte typer. Byte eller kort som følger opkoden utvides til en int før den skyves på bunken, fordi hvert spor på Java-stakken er 32 bits bredt. Operasjoner på byte og shorts som er dyttet på bunken gjøres faktisk på deres int-ekvivalenter.

OpcodeOperand (er)Beskrivelse
bipushbyte1utvider byte1 (en byte-type) til en int og skyver den på bunken
sipushbyte1, byte2utvider byte1, byte2 (en kort type) til en int og skyver den på bunken

Tre opkoder skyver konstanter fra det konstante bassenget. Alle konstanter tilknyttet en klasse, for eksempel verdier for endelige variabler, lagres i klassens konstante basseng. Opkoder som skyver konstanter fra det konstante bassenget har operander som indikerer hvilken konstant som skal presses ved å spesifisere en konstant bassengindeks. Den virtuelle Java-maskinen vil slå opp konstanten gitt indeksen, bestemme konstantens type og skyve den på bunken.

Den konstante bassengindeksen er en usignert verdi som umiddelbart følger opkoden i bytekodestrømmen. Opcodes lcd1 og lcd2 skyv et 32-biters element på bunken, for eksempel en int eller float. Forskjellen mellom lcd1 og lcd2 er det lcd1 kan bare referere til konstante bassengsteder en til 255 fordi indeksen er bare 1 byte. (Konstant bassengplassering null er ubrukt.) lcd2 har en 2-byte indeks, så den kan referere til en hvilken som helst konstant bassengplassering. lcd2w har også en 2-byte-indeks, og den brukes til å referere til en hvilken som helst konstant bassengplassering som inneholder en lang eller dobbel, som opptar 64 bits. Opkodene som skyver konstanter fra det konstante bassenget, er vist i følgende tabell:

OpcodeOperand (er)Beskrivelse
ldc1indeksbyte1skyver 32-biters konstant_pool-oppføring spesifisert av indexbyte1 på bunken
ldc2indexbyte1, indexbyte2skyver 32-biters konstant_pool-oppføring spesifisert av indexbyte1, indexbyte2 på bunken
ldc2windexbyte1, indexbyte2skyver 64-biters constant_pool-oppføring spesifisert av indexbyte1, indexbyte2 på bunken

Skyve lokale variabler på bunken

Lokale variabler lagres i en spesiell del av stabelrammen. Stabelrammen er den delen av stakken som brukes av den nåværende utførelsesmetoden. Hver stabelramme består av tre seksjoner - de lokale variablene, utførelsesmiljøet og operandstakken. Å skyve en lokal variabel på stakken innebærer faktisk å flytte en verdi fra delen av lokale variabler i stabelrammen til operand-delen. Operand-delen av den nåværende utførelsesmetoden er alltid toppen av stabelen, så å skyve en verdi på operand-delen av den nåværende stablerammen er det samme som å skyve en verdi på toppen av stabelen.

Java-stakken er en siste inn-først-ut-bunke med 32-bits spor. Fordi hvert spor i stabelen opptar 32 bits, opptar alle lokale variabler minst 32 bits. Lokale variabler av typen lang og dobbel, som er 64-biters mengder, opptar to spor på bunken. Lokale variabler av type byte eller kort lagres som lokale variabler av typen int, men med en verdi som er gyldig for den mindre typen. For eksempel vil en int lokal variabel som representerer en byte-type alltid inneholde en verdi som er gyldig for en byte (-128 <= verdi <= 127).

Hver lokale variabel i en metode har en unik indeks. Den lokale variabelseksjonen i en metodes stabelramme kan betraktes som en matrise med 32-bits spor, hver adresserbar av matriseindeksen. Lokale variabler av typen lang eller dobbel, som har to spor, blir referert til av den nedre av de to sporindeksene. For eksempel vil en dobbel som opptar spor to og tre bli referert til med en indeks på to.

Det finnes flere opkoder som skyver int og flyter lokale variabler på operandstakken. Noen opkoder er definert som implisitt refererer til en ofte brukt lokal variabel posisjon. For eksempel, iload_0 laster den int lokale variabelen i posisjon null. Andre lokale variabler skyves på stabelen av en opcode som tar den lokale variabelindeksen fra den første byten etter opcode. De iload instruksjon er et eksempel på denne typen opcode. Den første byten som følger iload tolkes som en usignert 8-biters indeks som refererer til en lokal variabel.

Usignerte 8-biters lokale variabelindekser, for eksempel den som følger iload instruksjon, begrense antall lokale variabler i en metode til 256. En egen instruksjon, kalt bred, kan utvide en 8-biters indeks med ytterligere 8 bits. Dette øker den lokale variabelgrensen til 64 kilobyte. De bred opcode etterfølges av en 8-bit operand. De bred opcode og dets operand kan gå foran en instruksjon, for eksempel iload, som tar en 8-biters usignert lokal variabelindeks. JVM kombinerer 8-bit operand av bred instruksjon med 8-biters operand av iload instruksjon om å gi en 16-biters usignert lokal variabelindeks.

Opkodene som skyver int og flyter lokale variabler på stakken, vises i følgende tabell:

OpcodeOperand (er)Beskrivelse
iloadvindexskyver int fra lokal variabel posisjon vindex
iload_0(ingen)skyver int fra lokal variabel posisjon null
iload_1(ingen)skyver int fra lokal variabel posisjon en
iload_2(ingen)skyver int fra lokal variabel posisjon to
iload_3(ingen)skyver int fra lokal variabel posisjon tre
flytevindexskyver flyte fra lokal variabel posisjon vindex
fload_0(ingen)skyver flyte fra lokal variabel posisjon null
fload_1(ingen)skyver flyte fra lokal variabel posisjon en
fload_2(ingen)skyver flyte fra lokal variabel posisjon to
fload_3(ingen)skyver flyte fra lokal variabel posisjon tre

Den neste tabellen viser instruksjonene som skyver lokale variabler av typen lang og dobbelt på bunken. Disse instruksjonene flytter 64 bits fra den lokale variable delen av stabelrammen til operand-delen.

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