Detaljno objašnjenje Java instrukcija bajtkoda — 10000 reči i 20 slika da potpuno savladate instrukcije bajtkoda
Instrukcije bajtkoda su prilično tvrd orah u sistemu JVM-a, i pretpostavljam da će se neki čitaoci zapitati: „Ovo je tako teško, hoću li uopšte moći da naučim?"
Iskreno, ne iz skromnosti: i meni je na početku učenja Java bajtkoda i Java virtuelne mašine bilo teško! Ali kada sam se uporno bavio time izvesno vreme, iznenada je „kliknulo" i počelo mi je da se dopada — naročito kada sam shvatio da se Java kod na najnižem nivou zapravo ovako izvršava, osetio sam se ponosno i sigurno, a samopouzdanje je sve prožimalo!
Hajde, učimo zajedno sa Ergeom, ne plašite se teškoća. Ranije smo već pomenuli da je JVM skup instrukcija bajtkoda zasnovan na steku, pa danas nastavljamo da učimo — šta su instrukcije bajtkoda.
Java instrukcije bajtkoda sastoje se od opkoda (opcode) i operanada:
- Opkod (Opcode): dužine jednog bajta (0–255, što znači da ukupan broj opkodova u skupu instrukcija ne može preći 256); predstavlja određeno specifično značenje operacije.
- Operandi (Operands): nula ili više, nalaze se neposredno iza opkoda, i predstavljaju parametre potrebne za tu operaciju.
Pošto je Java virtuelna mašina zasnovana na steku, a ne na registrima, većina instrukcija bajtkoda ima samo opkod. Na primer, aload_0 ima samo opkod bez operanada, dok invokespecial #1 sadrži i opkod i operand.
- aload_0: potiskuje podatak sa indeksom 0 iz tablice lokalnih promenljivih na stek operanada.
- invokespecial #1: poziva metodu instance ili konstruktor, i prosleđuje konstantu na indeksu 1 u bazenu konstanti.
Instrukcije bajtkoda se uglavnom dele na sledeće kategorije:
- Instrukcije učitavanja i skladištenja
- Aritmetičke instrukcije
- Instrukcije konverzije tipova
- Instrukcije kreiranja i pristupa objektima
- Instrukcije poziva metoda i povratka
- Instrukcije upravljanja stekom operanada
- Instrukcije kontrole toka
Hajde da ih redom objasnimo.
Instrukcije učitavanja i skladištenja
Instrukcije učitavanja (load) i skladištenja (store) su najčešće korišćene; služe za prenos podataka između tablice lokalnih promenljivih i steka operanada okvira steka.
Pogledajmo sledeći kod.
public int add(int a, int b) {
int result = a + b;
return result;
}Kada pomoću javap pregledamo instrukcije bajtkoda (grubo), dobijamo sledeće:
public int add(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: istore_3
4: ireturnSledećom slikom ću vam razjasniti proces izvršavanja instrukcija bajtkoda:

A sada da analiziramo konkretno značenje instrukcija load i store.
1) Potiskivanje promenljivih iz tablice lokalnih promenljivih na stek operanada
xload_<n>(x je i, l, f, d, a; n je podrazumevano od 0 do 3), označava potiskivanje n-te lokalne promenljive na stek operanada.- xload (x je i, l, f, d, a), putem zadatog argumenta potiskuje lokalnu promenljivu na stek operanada; kada se koristi ova instrukcija, to znači da broj lokalnih promenljivih možda prelazi 4.
Da objasnimo.
x je mnemonik opkoda koji ukazuje na koji tip podatka se odnosi. Pogledajte tablicu ispod.

Na primer, instrukcija arraylength nema mnemonik opkoda — nema posebnog znaka koji predstavlja tip podatka, ali operand može biti samo objekat tipa niza.
Većina instrukcija ne podržava bajt, short i char, a ni jedna instrukcija ne podržava tip boolean. Kompilator će podatke tipa bajt i short proširiti sa znakom (Sign-Extend) na tip int, a boolean i char proširiti nulom (Zero-Extend) na tip int.
Na primer.
private void load(int age, String name, long birthday, boolean sex) {
System.out.println(age + name + birthday + sex);
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode load() (4 argumenta).

iload_1: potiskuje int promenljivu na indeksu 1 iz tablice lokalnih promenljivih na stek operanada.aload_2: potiskuje promenljivu referentnog tipa podatka na indeksu 2 (ovde String) iz tablice lokalnih promenljivih na stek operanada.lload_3: potiskuje long promenljivu na indeksu 3 iz tablice lokalnih promenljivih na stek operanada.- iload 5: potiskuje int promenljivu na indeksu 5 iz tablice lokalnih promenljivih (zapravo boolean) na stek operanada.
Pregledom tablice lokalnih promenljivih sve se poklapa.

2) Potiskivanje konstanti iz bazena konstanti na stek operanada
Prema tipu podatka i sadržaju koji se potiskuje, može se dalje podeliti na seriju const, seriju push i instrukcije ldc.
Serija const, za potiskivanje posebnih konstanti; konstanta koja se potiskuje implicitno je sadržana u samoj instrukciji.

Serija push, uglavnom obuhvata bipush i sipush; prvi prima 8-bitni ceo broj kao argument, a drugi 16-bitni.
Instrukcija ldc, kada const i push nisu dovoljni, na scenu stupa univerzalna instrukcija ldc; ona prima jedan 8-bitni argument koji ukazuje na indeks u bazenu konstanti.
ldc_w: prima dva 8-bitna broja, čime je opseg indeksa veći.- Ako je argument long ili double, koristi se instrukcija
ldc2_w.
Na primer.
public void pushConstLdc() {
// opseg [-1,5]
int iconst = -1;
// opseg [-128,127]
int bipush = 127;
// opseg [-32768,32767]
int sipush= 32767;
// ostali int
int ldc = 32768;
String aconst = null;
String IdcString = "Chenmo Wang Er";
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode pushConstLdc().

iconst_m1: potiskuje -1. Opseg [-1,5].- bipush 127: potiskuje 127. Opseg [-128,127].
- sipush 32767: potiskuje 32767. Opseg [-32768,32767].
ldc #6 <32768>: potiskuje konstantu 32768 na indeksu 6 u bazenu konstanti.- aconst_null: potiskuje null.
ldc #7 <Chenmo Wang Er>: potiskuje konstantu „Chenmo Wang Er" na indeksu 7 u bazenu konstanti.
3) Skidanje podataka sa vrha steka i smeštanje u tablicu lokalnih promenljivih
Uglavnom služi za dodelu vrednosti lokalnim promenljivama; ovakve instrukcije pretežno postoje u obliku store.
xstore_<n>(x je i, l, f, d, a; n je podrazumevano od 0 do 3)- xstore (x je i, l, f, d, a)
Kada razumete xload_<n> i xload, čitanje xstore_<n> i xstore je mnogo lakše — uloga je samo obrnuta.
Razmislite o jednom pitanju: zašto uopšte postoje xstore_<n> i xload_<n>? Zar njihova uloga nije ista kao kod xstore n i xload n?
Razlika između xstore_<n> i xstore n je u tome što prvi ima samo opkod i zauzima 1 bajt; drugi se sastoji od opkoda i operanda — opkod zauzima 1 bajt, operand 2 bajta, ukupno 3 bajta.
Pošto su prve pozicije u tablici lokalnih promenljivih uvek veoma često korišćene, iako xstore_<n> i xload_<n> povećavaju broj instrukcija, zapremina bajtkoda se smanjuje!
Na primer.
public void store(int age, String name) {
int temp = age + 2;
String str = name;
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode store().

istore_3: skida jedan ceo broj sa steka operanada i dodeljuje ga promenljivoj na indeksu 3 u tablici lokalnih promenljivih.- astore 4: skida jednu vrednost referentnog tipa podatka sa steka operanada i dodeljuje je promenljivoj na indeksu 4 u tablici lokalnih promenljivih.
Pregledom tablice lokalnih promenljivih sve se poklapa.

Aritmetičke instrukcije
Aritmetičke instrukcije služe za izvršavanje određene operacije nad dve vrednosti na steku operanada i ponovno potiskivanje rezultata na stek operanada. Mogu se podeliti u dve kategorije: instrukcije za operacije nad celobrojnim podacima i instrukcije za operacije nad podacima u pokretnom zarezu.
U ovoj sekciji možete ponoviti Java operatore, čime ćete neke vrlo jednostavne aritmetičke operacije povezati sa JVM-om.
Treba imati na umu da aritmetičke operacije mogu dovesti do prelivanja (overflow) — na primer, sabiranje dva vrlo velika pozitivna cela broja verovatno daje negativan broj. Ali specifikacija Java virtuelne mašine ne definiše konkretan rezultat u takvoj situaciji, pa program neće eksplicitno prijaviti grešku. Zato prilikom razvoja, kada su u pitanju veliki brojevi u operacijama sabiranja i množenja, obratite posebnu pažnju!
Kada dođe do prelivanja, koristi se označena beskonačnost (Infinity) za prikaz; ako rezultat neke operacije nema jasnu matematičku definiciju, koristi se vrednost NaN. Takođe, sve aritmetičke operacije koje koriste NaN kao operand vratiće NaN kao rezultat.
Na primer.
public void infinityNaN() {
int i = 10;
double j = i / 0.0;
System.out.println(j); // Infinity
double d1 = 0.0;
double d2 = d1 / 0.0;
System.out.println(d2); // NaN
}- Bilo koji broj različit od nule podeljen brojem u pokretnom zarezu 0 (napomena: nije int tip), rezultat je beskonačnost Infinity.
- Kada se taj broj različit od nule zameni nulom, rezultat je teže definisati, pa se prikazuje vrednošću NaN.
Java virtuelna mašina pruža dva režima zaokruživanja:
- Zaokruživanje na najbliži: prilikom operacija sa brojevima u pokretnom zarezu, svi rezultati moraju biti zaokruženi na odgovarajuću preciznost; rezultati koji nisu sasvim tačni moraju se zaokružiti na najbližu predstavljivu tačnu vrednost, a ako postoje dva predstavljiva oblika jednako bliska toj vrednosti, prednost ima onaj čiji je najniži značajni bit nula (slično matematičkom zaokruživanju).
- Zaokruživanje prema nuli: koristi se prilikom konverzije broja u pokretnom zarezu u ceo broj; ovaj režim bira broj u ciljnom numeričkom tipu koji je najbliži, ali ne veći od originalne vrednosti, kao najtačniji rezultat zaokruživanja (slično odsecanju).
Navodim sve aritmetičke instrukcije:
- Instrukcije sabiranja: iadd, ladd, fadd, dadd
- Instrukcije oduzimanja: isub, lsub, fsub, dsub
- Instrukcije množenja: imul, lmul, fmul, dmul
- Instrukcije deljenja: idiv, ldiv, fdiv, ddiv
- Instrukcije ostatka: irem, lrem, frem, drem
- Instrukcije uvećanja: iinc
Na primer.
public void calculate(int age) {
int add = age + 1;
int sub = age - 1;
int mul = age * 2;
int div = age / 3;
int rem = age % 4;
age++;
age--;
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode calculate().

- iadd, sabiranje
- isub, oduzimanje
- imul, množenje
- idiv, deljenje
- irem, ostatak
- iinc, pri uvećanju +1, pri smanjenju -1
Instrukcije konverzije tipova
Instrukcije konverzije tipova mogu se podeliti u dve vrste:
- Proširenje (widening), konverzija manjeg tipa u veći, npr.
int–>long–>float–>double; odgovarajuće instrukcije su: i2l, i2f, i2d, l2f, l2d, f2d.
- Konverzija iz int u long, ili iz int u double, ne dovodi do gubitka preciznosti.
- Pri konverziji iz int i long u float, ili iz long u double, može doći do gubitka preciznosti.
- Proširenje iz bajt, char i short u int se zapravo dešava implicitno, čime se smanjuje broj instrukcija bajtkoda — budući da ih ima samo 256 i zauzimaju jedan bajt.
- Suženje (narrowing), konverzija većeg tipa u manji, npr. iz int u bajt, short ili char; odgovarajuće instrukcije su: i2b, i2s, i2c; iz long u int — l2i; iz float u int ili long — f2i, f2l; iz double u int, long ili float — d2i, d2l, d2f.
- Suženje vrlo lako može dovesti do gubitka preciznosti, budući da su u pitanju različiti redovi veličine.
- Ali Java virtuelna mašina zbog toga neće baciti izuzetak tokom izvršavanja.
Možete se podsetiti ranije obrađenog: Automatska i prisilna konverzija tipova
Na primer.
public void updown() {
int i = 10;
double d = i;
float f = 10f;
long ong = (long)f;
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode updown().

- i2d, proširenje iz int u double
- f2l, suženje iz float u long
Instrukcije kreiranja i pristupa objektima
Java je objektno orijentisani programski jezik, pa kako je Java virtuelna mašna podržava na nivou bajtkoda?
1) Instrukcije kreiranja
Niz je posebna vrsta objekta; instrukcije bajtkoda za kreiranje niza razlikuju se od instrukcija za kreiranje običnih objekata. Instrukcije za kreiranje niza su tri:
- newarray: kreira niz primitivnog tipa
- anewarray: kreira niz referentnog tipa
- multianewarray: kreira višedimenzionalni niz
Instrukcija za kreiranje objekta je samo jedna — new; ona prima jedan operand koji ukazuje na indeks u bazenu konstanti, a predstavlja tip koji treba kreirati.
Na primer.
public void newObject() {
String name = new String("Chenmo Wang Er");
File file = new File("dnevnik.book");
int [] ages = {};
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode newObject().

new #13 <java/lang/String>, kreira jedan String objekat.new #15 <java/io/File>, kreira jedan File objekat.newarray 10 (int), kreira niz tipa int.
2) Instrukcije pristupa poljima
Polja se mogu podeliti u dve kategorije: promenljive instance i statičke promenljive (odnosno promenljive klase), pa se i instrukcije pristupa poljima dele u dve kategorije:
- Pristup statičkim promenljivama: getstatic, putstatic.
- Pristup promenljivama instance: getfield, putfield; zahteva kreiranje objekta pre pristupa.
Na primer.
public class Writer {
private String name;
static String mark = "autor";
public static void main(String[] args) {
print(mark);
Writer w = new Writer();
print(w.name);
}
public static void print(String arg) {
System.out.println(arg);
}
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode main().

getstatic #2 <com/itwanger/jvm/Writer.mark>, pristupa statičkoj promenljivoj markgetfield #6 <com/itwanger/jvm/Writer.name>, pristupa promenljivoj instance name
Instrukcije poziva metoda i povratka
Ima 5 instrukcija poziva metoda, namenjenih različitim situacijama:
- invokevirtual: za poziv metoda instance objekta; vrši dispatch na osnovu stvarnog tipa objekta i podržava polimorfizam.
- invokeinterface: za poziv metoda interfejsa; u toku izvršavanja pretražuje metode interfejsa koje implementira određeni objekat i poziva ih.
- invokespecial: za poziv metoda koji zahtevaju posebno obrađivanje, uključujući konstruktore, privatne metode i metode roditeljske klase.
- invokestatic: za poziv statičkih metoda.
- invokedynamic: za dinamičko razrešavanje, u toku izvršavanja, metoda na koji ukazuje kvalifikator pozivne tačke, i njegovo izvršavanje.
Na primer.
public class InvokeExamples {
private void run() {
List ls = new ArrayList();
ls.add("tesko");
ArrayList als = new ArrayList();
als.add("ne-mogu-vise-da-ucim");
}
public static void print() {
System.out.println("invokestatic");
}
public static void main(String[] args) {
print();
InvokeExamples invoke = new InvokeExamples();
invoke.run();
}
}Pomoću javap -c InvokeExamples.class dekompajlirajmo.
Compiled from "InvokeExamples.java"
public class com.itwanger.jvm.InvokeExamples {
public com.itwanger.jvm.InvokeExamples();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
private void run();
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String tesko
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: new #2 // class java/util/ArrayList
20: dup
21: invokespecial #3 // Method java/util/ArrayList."<init>":()V
24: astore_2
25: aload_2
26: ldc #6 // String ne-mogu-vise-da-ucim
28: invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
31: pop
32: return
public static void print();
Code:
0: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #9 // String invokestatic
5: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #11 // Method print:()V
3: new #12 // class com/itwanger/jvm/InvokeExamples
6: dup
7: invokespecial #13 // Method "<init>":()V
10: astore_1
11: aload_1
12: invokevirtual #14 // Method run:()V
15: return
}Klasa InvokeExamples ima 4 metoda, uključujući podrazumevani konstruktor.
1) invokespecial
Unutar podrazumevanog konstruktora poziva se konstruktor inicijalizacije natklase Object:
`invokespecial #1 // Method java/lang/Object."<init>":()V`2) invokeinterface i invokevirtual
invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)ZPošto je referentni tip promenljive ls interfejs List, poziv ls.add() koristi instrukciju invokeinterface; tek u toku izvršavanja se utvrđuje da li je u pitanju metoda add() objekta ArrayList koji implementira interfejs List.
invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)ZPošto je referentni tip promenljive als već utvrđen kao ArrayList, poziv metode als.add() koristi instrukciju invokevirtual.
3) invokestatic
invokestatic #11 // Method print:()VMetoda print() je statička, pa se njen poziv obavlja instrukcijom invokestatic.
invokedynamic
Instrukcija invokedynamic uvedena je u Javi 7, uglavnom da bi podržala dinamičke jezike kao što su Groovy, Scala, JRuby itd. Ti jezici u toku izvršavanja dinamički razrešavaju metode na koje ukazuje kvalifikator pozivne tačke i izvršavaju ih.
Pogledajte sledeći kod, koji koristi Lambda izraz — implementacija Lambda izraza se oslanja baš na instrukciju invokedynamic:
import java.util.function.Function;
public class LambdaExample {
public static void main(String[] args) {
// pomocu Lambda izraza definise se funkcija
Function<Integer, Integer> square = x -> x * x;
// poziva se ta funkcija
int result = square.apply(5);
System.out.println(result); // ispisuje 25
}
}U ovom primeru, Lambda izraz x -> x * x definiše funkciju koja prima celi broj i vraća njegov kvadrat. Prilikom kompajliranja ovog koda, kompilator će koristiti instrukciju invokedynamic da dinamički poveže taj Lambda izraz.

①,invokedynamic #2, 0: pomoću invokedynamic poziva se bootstrap metoda (Bootstrap Method) koja je odgovorna za implementaciju i vraćanje instance interfejsa Function. Ovde je Lambda izraz x -> x * x pretvoren u Function objekat. Bootstrap metoda se poziva pri prvom izvršavanju i odgovorna je za generisanje CallSite-a, koji sadrži ručku metoda (Method Handle) koja ukazuje na konkretnu implementaciju Lambda izraza. U ovom primeru ta ručka metoda ukazuje na metodu lambda$main$0.
②,astore_1: rezultat instrukcije invokedynamic (Function objekat Lambda izraza) skladišti se na poziciju 1 u tablici lokalnih promenljivih.
③,Implementacija Lambda izraza je: lambda$main$0, što je stvarna implementacija izraza x -> x * x. Ona prima jedan Integer objekat kao argument, izračunava njegov kvadrat i vraća rezultat.
public class LambdaExample {
public LambdaExample() {
}
public static void main(String[] args) {
Function<Integer, Integer> square = (x) -> {
return x * x;
};
int result = (Integer)square.apply(5);
System.out.println(result);
}
}Ostale instrukcije ovde nećemo dalje analizirati; možete ih sami isprobati i proveriti šta ste naučili.
Instrukcije povratka metode
Instrukcije povratka metode razlikuju se prema tipu povratne vrednosti metode; uobičajene instrukcije povratka prikazane su na slici ispod — to su razni oblici return.

Instrukcije upravljanja stekom operanada
Uobičajene instrukcije upravljanja stekom operanada su pop, dup i swap.
- Skidanje jednog ili dva elementa sa vrha steka i njihovo odbacivanje, npr. pop, pop2.
- Kopiranje jedne ili dve vrednosti sa vrha steka i ponovno potiskivanje na vrh, npr. dup, dup2,
dup*×1,dup2*×1,dup*×2,dup2*×2. - Zamena mesta dve vrednosti u dva najgornja slota steka, npr. swap.
Ove instrukcije ne moraju da navode tip podatka, jer se potiskivanje i skidanje obavljaju prema poziciji.
Na primer.
public class Dup {
int age;
public int incAndGet() {
return ++age;
}
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode incAndGet().

aload_0: potiskuje this na stek.- dup: kopira this sa vrha steka.
getfield #2: učitava na stek konstantu na indeksu 2 u bazenu konstanti, a istovremeno skida jedan this sa steka.iconst_1: potiskuje konstantu 1 na stek.- iadd: sabira dve gornje vrednosti na steku, skida ih, i rezultat vraća na stek.
dup_x1: kopira element sa vrha steka i umeće ga ispod this.putfield #2: skida dva gornja elementa sa steka i dodeljuje ih polju age.- ireturn: skida element sa vrha steka i vraća ga.
Instrukcije kontrole toka
Instrukcije kontrole toka obuhvataju:
- Instrukcije poređenja, koje porede dva elementa na vrhu steka i rezultat poređenja potiskuju na stek.
- Instrukcije uslovnog skoka, obično se koriste zajedno sa instrukcijama poređenja; pre izvršenja instrukcije uslovnog skoka, instrukcijom poređenja se obično uporede elementi na vrhu steka, a zatim se vrši uslovni skok.
- Instrukcije poređenja i uslovnog skoka, slično kombinaciji instrukcija poređenja i uslovnog skoka — spaja korak poređenja i skoka u jedan.
- Instrukcije višestrukog grananja, namenjene iskazu switch-case.
- Instrukcije bezuslovnog skoka, trenutno uglavnom goto instrukcija.
Ovo se povezuje sa ranije obrađenim iskazima kontrole toka.
1) Instrukcije poređenja
Instrukcije poređenja su: dcmpg, dcmpl, fcmpg, fcmpl, lcmp; prvo slovo instrukcije označava redom double, float, long. Napomena: ne postoji tip int.
Za double i float, zbog postojanja NaN, postoje dve verzije instrukcija poređenja. Uzmimo float: postoje fcmpg i fcmpl; razlika je u tome što će, pri susretu sa NaN, fcmpg potisnuti 1 na stek, a fcmpl potisnuti -1.
Na primer.
public void lcmp(long a, long b) {
if(a > b){}
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode lcmp().

lcmp služi za poređenje dva podatka tipa long.
2) Instrukcije uslovnog skoka

Ove instrukcije primaju operande od po dva bajta; njihovo zajedničko značenje je: skinuti element sa vrha steka, proveriti da li ispunjava određeni uslov, i ako ispunjava, skočiti na odgovarajuću poziciju.
Za uslovno grananje i poređenje tipova long, float i double, prvo se izvršava instrukcija poređenja koja vraća celobrojnu vrednost na stek operanada, a zatim instrukcija uslovnog skoka za int.
Za boolean, bajt, char, short i int, koriste se direktno instrukcije uslovnog skoka.
Na primer.
public void fi() {
int a = 0;
if (a == 0) {
a = 10;
} else {
a = 20;
}
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode fi().

Značenje 3 ifne 12 (+9) je: ako element na vrhu steka nije jednak 0, skače se na 12. red (3+9), odnosno 12 bipush 20.
3) Instrukcije poređenja i uslovnog skoka

Nakon prefiksa „if_", instrukcije koje počinju slovom „i" operišu nad celim brojevima tipa int, dok instrukcije koje počinju slovom „a" označavaju poređenje objekata.
Na primer.
public void compare() {
int i = 10;
int j = 20;
System.out.println(i > j);
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode compare().

Značenje 11 if_icmple 18 (+7) je: ako pri poređenju dve vrednosti tipa int sa vrha steka prva bude manja od druge, skače se na 18. red (11+7).
4) Instrukcije višestrukog grananja
Uglavnom su to tableswitch i lookupswitch. Prvi zahteva da vrednosti više grana budu uzastopne — interno čuva samo početnu i završnu vrednost i više ofseta skoka; na osnovu zadatog operanda index, odmah se locira pozicija ofseta skoka, pa je efikasnost visoka. Drugi interno čuva diskretne parove case-offset; pri svakom izvršavanju mora da pretraži sve parove case-offset, pronađe podudarnu case vrednost i na osnovu odgovarajućeg offseta izračuna adresu skoka, pa je efikasnost niža.
Na primer.
public void switchTest(int select) {
int num;
switch (select) {
case 1:
num = 10;
break;
case 2:
case 3:
num = 30;
break;
default:
num = 40;
}
}Pogledajmo preko jclasslib-a instrukcije bajtkoda metode switchTest().

U slučaju case 2 nema break, pa su case 2 i case 3 uzastopni, te se koristi tableswitch. Ako je jednako 1, skače se na 28. red; ako je jednako 2 ili 3, skače se na 34. red; a ako je u pitanju default, skače se na 40. red.
5) Instrukcija bezuslovnog skoka
Instrukcija goto prima operande od po dva bajta, koji zajedno čine označeni ceo broj kojim se određuje ofset instrukcije; cilj izvršenja instrukcije jeste skok na poziciju zadanu ofsetom.
I u prethodnim primerima se pojavljivalo goto, što je lako razumeti. Ako je ofset instrukcije posebno velik i prelazi opseg dva bajta, može se koristiti instrukcija goto_w, koja prima 4 operanda od po jednog bajta.
Instrukcije bajtkoda pri obradi izuzetaka
Hajde da preko jednostavnog primera Java koda objasnimo instrukcije bajtkoda pri obradi izuzetaka.
public class ExceptionExample {
public void testException() {
try {
int a = 1 / 0; // ovo ce izazvati izuzetak deljenja nulom
} catch (ArithmeticException e) {
System.out.println("Došlo je do aritmetičkog izuzetka");
}
}
}Nakon kompajliranja ovog koda, pomoću javap -c ExceptionExample možemo pregledati njegov bajtkod, grubo ovako:
public void testException();
Code:
0: iconst_1
1: iconst_0
2: idiv
3: istore_1
4: goto 12
7: astore_1
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #3 // String Došlo je do aritmetičkog izuzetka
13: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: return
Exception table:
from to target type
0 4 7 Class java/lang/ArithmeticException①,Deljenje
Instrukcije 0: iconst_1, 1: iconst_0, 2: idiv izvršavaju operaciju deljenja 1 / 0. Prve dve instrukcije potiskuju konstante 1 i 0 na stek operanada, a zatim instrukcija idiv obavlja deljenje.
3: istore_1 skladišti rezultat deljenja u tablicu lokalnih promenljivih (ovde će doći do izuzetka, pa se instrukcija zapravo neće izvršiti).
②,Obrada izuzetka
4: goto 12: na kraju try bloka nalazi se instrukcija goto koja preskače kod catch bloka.
7: astore_1 označava početak catch bloka. Ako se uhvati izuzetak, objekat izuzetka se skladišti u tablicu lokalnih promenljivih.
8 - 13: getstatic, ldc, invokevirtual — ove instrukcije izvršavaju System.out.println("Došlo je do aritmetičkog izuzetka").
16: return — metoda se vraća.
Deo Exception table definiše rukovaoca izuzecima. U ovom primeru, kada se između ofseta bajtkoda 0 i 4 desi ArithmeticException, kontrola se prebacuje na ofset 7, odnosno na početak catch bloka.
Detaljno možete preko ovog primera povezati znanje sa ranije obrađenim Obradom izuzetaka.
Instrukcije bajtkoda za synchronized
Hajde da pogledamo još jedan primer sa ključnom reči synchronized — jednostavan sinhronizovani blok koda:
public class SynchronizedExample {
public void syncBlockMethod() {
synchronized(this) {
// telo sinhronizovanog bloka
}
}
}Odgovarajući bajtkod je grubo ovakav:
public void syncBlockMethod();
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_1
5: monitorexit
6: goto 14
9: astore 2
11: aload_1
12: monitorexit
13: aload 2
15: athrow
16: return
Exception table:
from to target type
4 6 9 any
9 13 9 anyInstrukcije monitorenter / monitorexit služe za označavanje početka i kraja sinhronizovanog bloka. Instrukcija monitorenter služi za preuzimanje monitor brave objekta, a monitorexit za oslobađanje brave.
Nadam se da ćete preko ovog jednostavnog primera povezati znanje sa ranije obrađenom ključnom reči synchronized.
Preporučeno čitanje: https://segmentfault.com/a/1190000037628881
Rezime
Put je dugačak i beskrajan, gore i dole ću tražiti istinu.
U ovoj sekciji detaljno smo predstavili Java instrukcije bajtkoda, uključujući aritmetičke instrukcije, instrukcije konverzije tipova, instrukcije za kreiranje i pristup objektima, instrukcije za poziv metoda i povratak, instrukcije za upravljanje stekom operanada, instrukcije kontrole toka, instrukcije bajtkoda pri obradi izuzetaka, instrukcije bajtkoda za synchronized itd.
Da biste otišli dalje, instrukcije bajtkoda morate temeljno da savladate — nadam se da vam Ergeina deljenja mogu pomoći~
