
Dziś zejdziemy poziom niżej w stosunku do kodu, z którym mamy styczność na co dzień. Postaram się pokazać wam co znajduje się w skompilowany pliku .class oraz jaki ma to wpływ na JVM’a na przykładzie aplikacji enterprise, czyli HelloWorld. W samym skompilowanym pliku oprócz kodu bajtowego z instrukcjami dla JVM, znajdziemy także metadane. Jeśli chcesz się dowiedzieć co dokładnie siedzi w środku, zapraszam!
HelloWorld
Na samym początku napiszemy sobie nic innego, jak prostego enterprisowego Hello World’a:
1 | public class Main { |
Po skompilowaniu kodu, sprawdzamy bytecode zawarty w pliku .class. Aby podejrzeć bytecode w postaci zjadliwej dla człowieka, a nie maszyny, możemy wykorzystać narzędzie javap, najlepiej z przełącznikiem -v (lub dłuższa wersja -verbose):
1 | javap -v Main |
W pliku .class dane przechowywane są w formacie HEX. Możemy podejrzeć nasz plik korzystając z dowolnego edytor’a HEX’ów (polecam command linowy xxd):
1 | 00000000: cafe babe 0000 0033 001d 0a00 0600 0f09 .......3........ |
No dobra, wiemy, że nasza metoda z HelloWorld zawarta jest w:
…b2 0002 12 03 b6 0004 b1 0000…
Czytając po kolei możemy odczytać:
- b2 -
getstatic- pobiera element statyczny - 12 -
ldc- ładuje wartość na stos z constant pool - b6 -
invokevirtual- wywołuje metodę i odkłada wynik na stosie (tutaj mamy wypisywanie, więc nic nie odkładamy na stosie) - b1 -
return- to nic innego jak wyjście z metody
Jeśli wrócimy do pliku w postaci HEX, widzimy, iż w tym pliku znajduje się dużo więcej informacji. Oprócz kodu bajtowego znajdują się tutaj także informacje o klasie, które opisane są przez:
My Very Cute Animal Turns Savage In Full Moon Areas

Każda litera tego zdania ma znaczenie. Już wyjaśniam.
P.S muszą być koty!
Struktura
Zanim przejdziemy do wyjaśnień opowieści o kocie, należy zapoznać się ze strutkturą skompilowanej klasy:
1 | ClassFile { |
M - Magic Number
Magiczny numer, wykorzystywany jest do jednoznacznego identyfikowania typu pliku. W przypadku plików .class jest to HEX o wartości 0xCAFEBABE. Mimo, iż pierwsze skojarzenia prowadzą nas na “Java, kawa więc pewnie dlatego CAFE BABE“, jednakże historia tego magic number jest trochę inna:
“We used to go to lunch at a place called St Michael’s Alley. According to local legend, in the deep dark past, the Grateful Dead used to perform there before they made it big. It was a pretty funky place that was definitely a Grateful Dead Kinda Place. When Jerry died, they even put up a little Buddhist-esque shrine. When we used to go there, we referred to the place as Cafe Dead. Somewhere along the line it was noticed that this was a HEX number. I was re-vamping some file format code and needed a couple of magic numbers: one for the persistent object file, and one for classes. I used CAFEDEAD for the object file format, and in grepping for 4 character hex words that fit after “CAFE” (it seemed to be a good theme) I hit on BABE and decided to use it. At that time, it didn’t seem terribly important or destined to go anywhere but the trash-can of history. So CAFEBABE became the class file format, and CAFEDEAD was the persistent object format. But the persistent object facility went away, and along with it went the use of CAFEDEAD - it was eventually replaced by RMI.
Jeśli wartość ta jest niepoprawna, JVM rzuca wyjątek typu java.lang.ClassFormatError.
V - Version
Kolejna wartość określa wersję w jakiej został wygenerowany plik. Jeśli JVM wykryje, iż wersja pliku .class jest niewspierana, dostaniemy wyjątek typu java.lang.UnsupportedClassVersionError. Tak prezentują się wartości wersji:
- Java SE 10 = 54 (0x36 hex)
- Java SE 9 = 53 (0x35 hex)
- Java SE 8 = 52 (0x34 hex)
- Java SE 7 = 51 (0x33 hex)
- Java SE 6.0 = 50 (0x32 hex)
- mniejsze numery - wcześniejsze wersje
W naszym HelloWorld (Java 7 here ;)):
1 | 00000000: cafe babe 0000 0033 001d 0a00 0600 0f09 .......3........ |
C - Constant Pool
Pula ta przechowuje wszystkie informacje o stałych w klasie, między innymi nazwy pól czy metod:
1 | Constant pool: |
Informacje z tej puli ładowane są do obszaru pamięci zwanego permament generation (dokładniej do metaspace).
A - Access Flags
W tej części znajdziemy informacje na temat flag dostępowych do klasy:
| Nazwa flagi | Wartość |
|---|---|
| ACC_PUBLIC | 0x0001 |
| ACC_FINAL | 0x0010 |
| ACC_SUPER | 0x0020 |
| ACC_INTERFACE | 0x0200 |
| ACC_ABSTRACT | 0x0400 |
| ACC_SYNTHETIC | 0x1000 |
| ACC_ANNOTATION | 0x2000 |
| ACC_ENUM | 0x4000 |
T - This Class
Na kolejnych dwóch bajtach znajdziemy informacje o aktualnej klasie. Tak naprawdę przechowywany jest tutaj indeks do wpisu w Constant Pool’u, gdzie znajdują się bardziej szczegółowe informacje jak nazwa, czy typ klasy.
S - Super Class
Podobnie jak This Class zawiera indeks do wpisu w Constant Pool’u. W naszym przypadku klasą SUPER dla klasy Main jest Object.
IFMA - Interfaces, Fields, Methods i Attributes
Tak jak wskazują nazwy, kolejne obszary zawierają informacje o interfejsach wykorzystywanych w klasie, polach, metodach oraz o dodatkowych atrybutach.
Ale po co mi to wszystko?
Wyobraźmy sobie skrajną sytuację, w której korzystamy z biblioteki X. Okazało się, iż w bibliotece X, ważna dla nas metoda jest typu private (a miała być public w wersji 1.1.1). Robisz zgłoszenie na GitHub’ie, okazuje się, że rzeczywiście jest błąd, jednakże nowa wersja będzie wydana dopiero za pół roku… Jedyne co możemy zrobić, to zmodyfikować plik .class. Nigdy nie mamy pewności, że ten błąd zostanie naprawiony za pół roku. Oczywiście, jest to sytuacja skrajna, ale czasem życie zmusza nas do takich rozwiązań.
Ponadto na off-heapie w obszarze metaspace znajdują się dane z załadowanej przez classloader klasy. Informacje, które zostały załadowane (z pliku .class) i są wykorzystywane w aplikacji. Warto wiedzieć, gdzie przed załadowaniem takie informacje się znajdują. Warto mieć także świadomość, iż pliki .class nie są zbytnio bezpieczne, jeśli możemy bez problemu zmienić modyfikator dostępu ;).