Detaljno objašnjenje strukture Java class fajlova (struktura .class fajla)
Danas ću uzeti nožić i povesti vas da zajedno rassecimo strukturu Java class fajla, odnosno sadržajnu strukturu .class fajla. Iako je on zapravo neprekidni niz bita sastavljen od nula i jedinica, možemo uz pomoć alata jasno sagledati njegovo pravo lice.
Struktura class fajla = struktura .class fajla = Class struktura fajla — ova tri naziva znače istu stvar. „.class" se odnosi na ekstenziju fajla, „Class" posmatrano iz ugla Java klase, a „struktura class fajla" je srpski prevod te pojave.
--- Ovaj deo sadržaja je zapravo već ranije obrađen, ali je zadržan radi celovitosti ovog teksta; oni koji već vladaju gradivom mogu ga preskočiti start ----
U svetu računara kruži jedna izreka: „Svaki problem u računarskim naukama može se rešiti dodavanjem još jednog posredničkog sloja". Za Javu je JVM upravo takav proizvod. Parola „Write once, Run anywhere" ostvariva je upravo zahvaljujući JVM-u, koji na različitim operativnim sistemima može da pokrene isti class fajl dobijen prevođenjem istog izvornog koda.

Java je platformski nezavisna, pa JVM, kao posrednički sloj, prirodno mora da ponudi različite implementacije za različite operativne sisteme. Uzmimo JDK 11 — njegove implementacije obuhavaju upravo one prikazane na slici (trenutno najnovija verzija već je JDK 21).

Zahvaljujući JVM-u za različite operativne sisteme, naš izvorni kod ne mora da se prevodi u različite binarne izvršne fajlove za svaki operativni sistem, čime se cilj platformski nezavisnosti i ostvaruje.
Pa šta je zapravo taj class fajl? Kako ga JVM prepoznaje?
Napišimo u IDEA-i jednostavan Java kod pod imenom Hello.java.
package com.itwanger.jvm;
class Hello {
public static void main(String[] args) {
System.out.println("Hello!");
}
}Kada kliknete na dugme za prevođenje (ne morate ni ručno — IDEA automatski prevodi), IDEA će za nas generisati fajl Hello.class u odgovarajućem direktorijumu paketa pod target/classes. Kada ga direktno otvorite dvoklikom, izgleda ovako:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.itwanger.jvm;
class Hello {
Hello() {
}
public static void main(String[] args) {
System.out.println("Hello!");
}
}Izgleda vrlo slično izvornom kodu, samo sa dodatnim praznim konstruktorom, zar ne? To je izgled class fajla nakon što ga je Fernflower, alat za dekompilaciju ugrađen u IDEA-u, dekompilirao. A kako izgleda pravi class fajl?
Možete ga pregledati u terminalu komandom xxd Hello.class (ovo smo već ranije spominjali, možete kliknuti na ovaj link kako biste se podsetili).

Ovo je heksadecimalni oblik class fajla.
--- Ovaj deo sadržaja je zapravo već ranije obrađen, ali je zadržan radi celovitosti ovog teksta; oni koji već vladaju gradivom mogu ga preskočiti end ----
Sadržaj class fajla se obično može podeliti na sledeće delove, kao na slici.

01. Magicni broj
Pogledajmo ponovo snimak heksadecimalnog oblika class fajla.
U prvom redu se nalazi niz posebnih znakova cafebabe — to je magicni broj (magic number), oznaka po kojoj JVM prepoznaje class fajl. JVM u fazi verifikacije proverava da li class fajl počinje ovim magicnim brojem, i ako ne počinje, baca ClassFormatError.
Značenje magicnog broja cafebabe prilično je očigledno — „coffee babe". S obzirom na to da je i logo Jave ustvari šoljica vruće kafe, vidi se koliko je duboka veza između Jave i kafe.

02. Verzijski broj
Četiri bajta koja neposredno slede iza magicnog broja, 0000 0037, predstavljaju redom minor (sporedni) i major (glavni) verzijski broj. Drugim rečima, glavni verzija je 55 (dekadska vrednost 0x37), što odgovara verziji Java 11, dok je sporedna verzija 0.
Prethodna LTS verzija bila je Java 8 sa glavnom verzijom 52, što znači da je Java 9 verzija 53, a Java 10 verzija 54. Međutim, Java 9 i Java 10 su prelazne verzije; sledeća LTS verzija je Java 17, čiji se izlazak očekivao u septembru 2021. godine (otud možete zaključiti vreme pisanja prve verzije ovog teksta, ha ha).
Danas je 14. decembar 2023. godine i Java 21 je već objavljena. Na osnovu gore opisanog postupka možete proveriti koja verzija odgovara Javi 21 — ostavljam vam kao mali domaći zadatak; pomažite se rukama i otkrićete sasvim drugi svet.
03. Constant pool
Odmah iza verzijskog broja nalazi se constant pool (bazen konstanti). On sadrži simboličke reference na klase, interfejse, polja i metode, kao i stringovne literale i numeričke konstante. Ovi podaci nastaju u trenutku prevođenja i Java virtuelna mašina (JVM) ih koristi tokom izvršavanja.
To je poput skladišta resursa u kojem se uglavnom čuvaju dve velike kategorije konstanti:
- Literali (Literals): literal je nepromenljiv podatak, prvenstveno numeričke vrednosti (celobrojne, pokretne tačke) i stringovni literal. Na primer, celi broj 100 ili string „Hello World" u izvornom kodu direktno se dodeljuju i nakon prevođenja čuvaju u constant pool-u.
- Simboličke reference (Symbolic References): simbolička referenca je upućivanje na klasu, interfejs, polje, metodu i sl., ali nije data prostom literalnom vrednošću već preko simboličkog imena (npr. imena klase, imena metode) i dodatnih podataka (npr. tip, potpis). U class fajlu one postoje u apstraktnom obliku, a virtuelna mašina ih pri učitavanju klase razrešava na konkretne memorijske adrese.
(Ovaj deo smo već ranije obradili; kliknite na link da se podsetite.)
Hajde da sada kroz konkretne primere koda pogledamo šta je zapravo constant pool.
Java definiše osnovne tipove podataka poput boolean, bajt, short, char i int, koji se u constant pool-u svi tretiraju kao int. Pogledajmo to kroz kratki Java kod.
public class ConstantTest {
public final boolean bool = true;
public final char aChar = 'a';
public final byte b = 66;
public final short s = 67;
public final int i = 68;
}Heksadecimalna vrednost boolean vrednosti true je 0x01, znaka a je 0x61, bajta 66 je 0x42, short vrednosti 67 je 0x43, a int vrednosti 68 je 0x44. Prema tome, mesta celobrojnih konstanti dobijenih prevođenjem u class fajlu prikazana su na slici ispod.

Prvi bajt 0x03 označava tip konstante CONSTANT_Integer_info — jedan od 14 tipova konstanti koje definiše JVM. Odgovarajući tipovi su još i CONSTANT_Float_info, CONSTANT_Long_info, CONSTANT_Double_info, sa identifikatorima 0x04, 0x05 odnosno 0x06.
Prikazaću to kraće tabelom:
| Tip konstante | Identifikator | Opis |
|---|---|---|
| CONSTANT_Integer_info | 0x03 | literal tipa int |
| CONSTANT_Float_info | 0x04 | literal tipa float |
| CONSTANT_Long_info | 0x05 | literal tipa long |
| CONSTANT_Double_info | 0x06 | literal tipa double |
Za int i float to su 4 bajta; za long i double 8 bajtova. Uzmimo maksimalnu vrednost tipa long da to posmatramo.
public class ConstantTest {
public final long ong = Long.MAX_VALUE;
}Pogledajmo njenu poziciju u class fajlu. Počinje sa 05, završava se sa 7f ff ff ff ff ff ff ff — zaista zauzima 8 bajtova. Ranije sam znao da long zauzima 8 bajtova, ali nisam imao intuitivan osećaj, a sada ga imam (😁).

Hajde da pogledamo i sledeći fragment koda.
class Hello {
public final String s = "hello";
}„hello" je string čija je heksadecimalna vrednost 68 65 6c 6c 6f; pogledajmo gde se nalazi u class fajlu.

Ispred njega su još 3 bajta: prvi bajt 0x01 je identifikator koji označava tip CONSTANT_Uft8_info, a drugi i treći (0x00 0x05) označavaju dužinu trećeg dela — niza bajtova, kao na slici.

U paru sa tipom CONSTANT_Uft8_info postoji i CONSTANT_String_info, koji predstavlja referencu na string objekat (s u prethodnom kodu), sa identifikatorom 0x08. Prvi čuva stvarnu vrednost stringa, dok drugi ne sadrži sadržaj stringa već samo indeks koji upućuje na CONSTANT_Uft8_info u constant pool-u.
Ovo se povezuje sa onim što smo ranije rekli o objektima i referencama — da li primetiš povezanost? (😁)
Pogledajmo gde se nalazi u class fajlu.

CONSTANT_String_info pomoću indeksa 19 pronalazi CONSTANT_Uft8_info, kao na slici.

Pored ovoga, postoji i CONSTANT_Class_info za predstavljanje klasa i interfejsa. Slično kao CONSTANT_String_info, prvi bajt je identifikator vrednosti 0x07, a sledeća dva bajta su indeks u constant pool-u koji upućuje na CONSTANT_Utf8_info — string koji čuva potpuno kvalifikovano ime klase ili interfejsa.
Za klasu Hello.java potpuno kvalifikovano ime je com/itwanger/jvm/Hello, a odgovarajuća heksadecimalna vrednost glasi „636f6d2f697477616e6765722f6a766d2f48656c6c6f" — to je jedan CONSTANT_Uft8_info. Gde se u class fajlu nalazi CONSTANT_Class_info koji na njega upućuje?
Zadržimo trenutak; ovde ću vam predstaviti alat za vizualizaciju bytecode-a jclasslib bytecode viewer (koji smo već ranije spominjali), koji se može instalirati direktno iz IDEA-ine prodavnice dodataka. Nakon instalacije, izaberite class fajl, a zatim u meniju View pronađite podmeni Show Bytecode With Jclasslib i moći ćete da pregledate ključne informacije class fajla.

Na gornjoj slici se vidi da je ukupna veličina constant pool-a 23, te da CONSTANT_Class_info sa indeksom 04 upućuje na CONSTANT_Uft8_info sa indeksom 21, čija je vrednost com/itwanger/jvm/Hello. Heksadecimalna vrednost broja 21 je 0x15; sa ovim podatkom možemo pronaći mesto CONSTANT_Class_info u class fajlu.

0x07 je prvi bajt — identifikator CONSTANT_Class_info — a zatim slede dva bajta koji koduju indeks.
Postoji još i CONSTANT_NameAndType_info koji označava polje ili metod, sa identifikatorom 12, odnosno heksadecimalno 0x0c. Iza njega slede još 4 bajta: prva dva su indeks imena polja ili metode, a druga dva deskriptor polja ili metode, odnosno tip polja ili metode.
Pogledajmo sledeći kod.
class Hello {
public void testMethod(int id, String name) {
}
}Pomoću jclasslib-a se vidi da CONSTANT_NameAndType_info sadrži dva indeksa.

Jedan je 4, drugi je 5; sastav CONSTANT_NameAndType_info može se prikazati sledećom slikom.

Položaj u class fajlu prikazan je na slici ispod.

Slede CONSTANT_Fieldref_info, CONSTANT_Methodref_info i CONSTANT_InterfaceMethodref_info; njih tri imaju sličnu strukturu koja se može prikazati sledećim pseudokodom.
CONSTANT_*ref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}Ko je učio C jezik i simboličku tabelu (Symbol Table), ovaj pseudokod mu neće biti nepoznat.
- tag je identifikator: za Fieldref je 9, heksadecimalno 0x09; za Methodref 10, heksadecimalno 0x0a; za InterfaceMethodref 11, heksadecimalno 0x0b.
- class_index je indeks u constant pool-u za CONSTANT_Class_info, koji predstavlja informaciju o klasi u kojoj se nalazi polje | metod | metod interfejsa.
- name_and_type_index je indeks u constant pool-u za CONSTANT_NameAndType_info: za Fieldref označava ime polja i tip polja; za Methodref ime metode, argumente i tip povratne vrednosti; za InterfaceMethodref ime metoda interfejsa, argumente i tip povratne vrednosti.
Ima još i CONSTANT_MethodHandle_info, CONSTANT_MethodType_info i CONSTANT_InvokeDynamic_info; evo tabele:
| Tip konstante | Identifikator | Opis |
|---|---|---|
| CONSTANT_MethodHandle_info | 0x0f | metod handle |
| CONSTANT_MethodType_info | 0x10 | tip metode |
| CONSTANT_InvokeDynamic_info | 0x12 | dinamička tačka poziva |
| CONSTANT_Fieldref_info | 0x09 | polje |
| CONSTANT_Methodref_info | 0x0a | običan metod |
| CONSTANT_InterfaceMethodref_info | 0x0b | metod interfejsa |
| CONSTANT_Class_info | 0x07 | potpuno kvalifikovano ime klase ili interfejsa |
| CONSTANT_String_info | 0x08 | string literal |
| CONSTANT_Uft8_info | 0x01 | string |
Eto, najkomplikovaniji deo class fajla — constant pool — je konačno raspolovljen, zaista nije bilo lako!
04. Access flags
Oblast koja neposredno sledi iza constant pool-a jesu access flags (pristupne oznake). Te oznake služe za prepoznavanje pristupnih informacija klase ili interfejsa, na primer:
- Da li je u pitanju class klasa ili interface interfejs?
- Da li je public?
- Da li je abstract apstraktna klasa?
- Da li je final klasa?
- Itd.
Ukupno je na raspolaganju 16 oznaka, ali se u praksi redovno koristi samo 7 njih, kao na slici.

Prikazano u tabeli:
| Oznaka | Identifikator | Opis |
|---|---|---|
| 0x0001 | ACC_PUBLIC | tip public |
| 0x0010 | ACC_FINAL | tip final |
| 0x0020 | ACC_SUPER | pri pozivu metoda roditeljske klase koristi se instrukcija invokespecial |
| 0x0200 | ACC_INTERFACE | tip interfejsa |
| 0x0400 | ACC_ABSTRACT | tip apstraktne klase |
| 0x1000 | ACC_SYNTHETIC | označava klasu koju je automatski generisao kompilator |
| 0x2000 | ACC_ANNOTATION | označava klasu napomene (anotaciju) |
| 0x4000 | ACC_ENUM | označava klasu enumeracije |
| 0x8000 | ACC_MODULE | označava modularnu klasu |
Pogledajmo jednostavan kod sa enumeracijom.
public enum Color {
RED,GREEN,BLUE;
}Pomoću jclasslib-a se vidi da informacija o access flags glasi 0x4031 [public final enum].

Položaj u class fajlu prikazan je na slici ispod.

05. Class index, superclass index i interface index
Ova tri dela služe za određivanje relacije nasleđivanja klase: this_class je indeks tekuće klase, super_class indeks roditeljske klase, a interfaces interfejsa.
Pogledajmo sledeći kratak kod — bez interfejsa, podrazumevano nasleđuje klasu Object.
class Hello {
public static void main(String[] args) {
}
}Pomoću jclasslib-a možemo videti relaciju nasleđivanja klase.

- this_class upućuje na CONSTANT_Class_info sa indeksom 2 u constant pool-u.
- super_class upućuje na CONSTANT_Class_info sa indeksom 3 u constant pool-u.
- Pošto nema interfejsa, informacija o interfaces je prazna.
Položaj u class fajlu prikazan je na slici ispod.

06. Tabela polja
Polja definisana u klasi čuvaju se u tabeli polja (fields), uključujući statyczna i nestatyczna.
Pogledajmo sledeći kod.
public class FieldsTest {
private String name;
}Ima samo jedno polje; modifikator mu je private, tip String, a ime polja name. Strukturu polja možemo prikazati sledećim pseudokodom.
field_info {
u2 access_flag;
u2 name_index;
u2 description_index;
}- access_flag je pristupna oznaka polja, npr. da li je public | private | protected, da li je static, da li je final i sl.
- name_index je indeks imena polja; upućuje na CONSTANT_Utf8_info u constant pool-u — u prethodnom primeru vrednost je name.
- description_index je indeks deskriptora tipa polja; takođe upućuje na CONSTANT_Utf8_info u constant pool-u, a za različite tipove podataka vrede različita pravila opisa.
Za osnovne tipove podataka koristi se jedan znak: tako I odgovara tipu int, a B tipu bajt.
Za referentne tipove podataka koristi se oblik
L***;— počinje saL, završava sa;; na primer, tip stringa jeLjava/lang/String;.Za nizove se ispred dodaje
[; na primer, niz stringova je[Ljava/lang/String;.
Položaj u class fajlu prikazan je na slici ispod.

Kada stignete do ovde, verujem da vam je jasno značenje onih čudnih znakova koje često viđate u izlazu komande javap.
07. Tabela metoda
Tabela metoda je slična tabeli polja, s tom razlikom što čuva podatke o metodima, uključujući ime metode, argumente i potpis metode.
Uzmimo metod main.
public class MethodsTest {
public static void main(String[] args) {
}
}Prvo preko jclasslib-a pogledajmo grubu strukturu.

- Pristupna oznaka je public static.
- Ime metode je main.
- Argument metode je niz stringova; tip povratne vrednosti je Void.
Položaj u class fajlu prikazan je na slici ispod.

08. Tabela atributa
Tabela atributa je poslednji deo class fajla i obično se javlja u sklopu polja i metoda.
Pogledajmo sledeći kod.
public class AttributeTest {
public static final int DEFAULT_SIZE = 128;
}Tu je samo konstanta DEFAULT_SIZE; ona spada u polja — naime, to je statyczna promenljiva sa final. Preko jclasslib-a pogledajmo jedan njen vrlo važan atribut — ConstantValue, koji predstavlja početnu vrednost statyczne promenljive.

- Attribute name index upućuje na konstantu čija je vrednost „ConstantValue" u constant pool-u.
- Attribute length ima fiksnu vrednost 2, jer indeks zauzima samo dva bajta.
- Constant value index upućuje na konkretnu konstantu u constant pool-u; ako je tip konstante int, upućuje na CONSTANT_Integer_info.
Nacrtao sam crtež koji u potpunosti prikazuje strukturu polja, uključujući i tabelu atributa.

Položaj u class fajlu prikazan je na slici ispod.

Pogledajmo sledeći kod.
public class MethodCode {
public static void main(String[] args) {
foo();
}
private static void foo() {
}
}U metod main poziva se metod foo. Preko jclasslib-a pogledajmo jedan njegov vrlo važan atribut — Code, u kojem se čuvaju ključne informacije metode.

- Attribute name index upućuje na konstantu čija je vrednost „Code" u constant pool-u.
- Attribute length je dužina vrednosti atributa.
- bytecode čuva stvarne instrukcije bytecode-a.
- exception table predstavlja informacije o izuzecima unutar metode.
- maximum stack size označava maksimalnu dubinu operand steka; u bilo kom trenutku izvršenja metode dubina operand steka neće preći tu vrednost.
- maximum local variable označava veličinu tabele privremenih promenljivih; to nije jednako zbiru svih privremenih promenljivih u metodi — kada se jedan opseg završi, mesto privremenih promenljivih unutar njega se prepisuje.
- code length je dužina instrukcija bytecode-a.
Položaj u class fajlu prikazan je na slici ispod.

09. Pitanja i odgovori
U komentarima jedan čitalac pita: „Kako se preko vrednosti indeksa određuje pozicija u class fajlu — kako se to računa?"
U Java class fajlu constant pool je indeksna tabela koja počinje da broji od indeksa 1, te svaka stavka ima jedinstveni indeks.
- Brojač constant pool-a: pre constant pool-a class fajl sadrži 16-bitni brojač constant pool-a koji navodi koliko stavki ima constant pool. Njegova vrednost je za jedan veća od stvarnog broja konstanti (jer indeksiranje kreće od 1).
- Stavke constant pool-a: svaka stavka počinje tagom (1 bajt) koji ukazuje na tip konstante (npr. Class, Fieldref, Methodref itd.). Na osnovu tog tipa razlikuje se i struktura podataka koja sledi.
Proces lociranja grubo izgleda ovako:
- Čitanje brojača constant pool-a: prvo se s početka class fajla pročita vrednost brojača constant pool-a, čime se utvrđuje broj stavki.
- Obilazak constant pool-a: kreće se od prve stavke. Pošto konstante različitih tipova imaju različitu dužinu, dužina svake se određuje na osnovu njenog tipa.
- Lociranje po indeksu: obilazak se nastavlja sve dok se ne stigne do traženog indeksa. Pri svakom obilasku se, na osnovu tipa stavke, pročita odgovarajuća dužina podataka, sve do ciljnog indeksa.
Možemo to apstrahovati na niz i for petlju i postaje jasno.
int[] constantPool = new int[constantPoolCount];
for (int i = 1; i < constantPoolCount; i++) {
int tag = constantPool[i];
switch (tag) {
case CONSTANT_Integer_info:
i += 4;
break;
case CONSTANT_Float_info:
i += 4;
break;
case CONSTANT_Long_info:
i += 8;
break;
case CONSTANT_Double_info:
i += 8;
break;
case CONSTANT_Utf8_info:
int length = constantPool[i + 1];
i += length + 1;
break;
case CONSTANT_String_info:
i += 2;
break;
case CONSTANT_Class_info:
i += 2;
break;
case CONSTANT_Fieldref_info:
i += 4;
break;
case CONSTANT_Methodref_info:
i += 4;
break;
case CONSTANT_InterfaceMethodref_info:
i += 4;
break;
case CONSTANT_NameAndType_info:
i += 4;
break;
case CONSTANT_MethodHandle_info:
i += 3;
break;
case CONSTANT_MethodType_info:
i += 2;
break;
case CONSTANT_InvokeDynamic_info:
i += 4;
break;
default:
throw new RuntimeException("Unknown tag: " + tag);
}
}10. Kratak pregled
Time je unutrašnjost class fajla prilično detaljno rascepljena; nadam se da će vam pomoći. Prvi put držim nožić u ruci, pa se malo trese — ukoliko negde ima nedostataka, slobodno i bez ustezanja mi ih ukažite u komentarima!
- Class fajl je neprekidan niz bita sastavljen od nula i jedinica, ali možemo uz pomoć alata jasno sagledati njegovo pravo lice.
- Sadržaj class fajla se obično deli na sledeće delove: magicni broj, verzijski broj, constant pool, access flags, class index, superclass index, interface index, tabela polja, tabela metoda i tabela atributa.
- Constant pool sadrži simboličke reference na klase, interfejse, polja i metode, kao i stringovne literale i numeričke konstante.
- Access flags služe za prepoznavanje pristupnih informacija klase ili interfejsa, npr. da li je public | private | protected, da li je static, da li je final itd.
- Class index, superclass index i interface index određuju relaciju nasleđivanja klase.
- Tabela polja čuva podatke o poljima, uključujući ime polja, argumente i potpis polja.
- Tabela metoda čuva podatke o metodima, uključujući ime metode, argumente i potpis metode.
- Tabela atributa čuva podatke o atributima, uključujući početnu vrednost polja, instrukcije bytecode-a metoda i sl.
Verujem da vam posle ovog teksta class fajl više neće biti nepoznanica.
