Learn · concepts

Czym jest ładowacz klas Java?

Ładowacz klas wyszukuje i ładuje klasy Java do JVM w czasie działania programu. Oto jak one działają, dlaczego ważna jest hierarchia i dlaczego mody od nich zależą.

TRtrolPublished 4 min read

Ładowacz klas to część środowiska uruchomieniowego Java, która znajduje plik klasy i ładuje go do JVM. Za każdym razem, gdy Twój kod odwołuje się do typu po raz pierwszy, JVM pyta ładowacz klas: "Znajdź mi tę klasę." Ładowacz lokalizuje bytecode, odczytuje go, a JVM definiuje klasę w pamięci. Ten mechanizm jest niewidoczny w normalnych programach Java, ale stanowi dokładny punkt zaczepienia, który wykorzystują frameworki modyfikacji do łatania Minecrafta.

Co tak naprawdę robi ładowacz klas

Ładowacz klas przekształca nazwę klasy w używaną klasę w działającym JVM. Prosisz o typ, ładowacz znajduje pasujący plik .class (lub generuje go), weryfikuje i łączy go, a klasa jest gotowa do użycia.

Praca odbywa się w trzech krokach:

  1. Znajdź — Ładowacz lokalizuje bytecode dla nazwanej klasy (z pliku JAR, dysku, sieci lub generowanego w locie).
  2. Zweryfikuj i połącz — JVM sprawdza, czy bytecode jest poprawny i rozwiązuje odwołania do innych klas.
  3. Użyj — Klasa jest gotowa i Twój program może ją instancjować.

Dlatego programy Java mogą dynamicznie ładować kod w czasie działania, który nigdy nie został wspomniany w czasie kompilacji. Nazwa klasy wystarczy ładowaczowi, aby go odnaleźć.

Hierarchia ładowaczy klas

Ładowacze klas są ułożone w hierarchię nadrzędną, a większość z nich najpierw pyta swojego nadrzędnego. Standardowa aplikacja Java ma trzy wbudowane ładowacze klas:

ŁadowaczŁadowaniaCel
BootstrapPodstawowe klasy środowiska uruchomieniowego JavaZapewnia, że podstawowe typy, takie jak java.lang.Object, są zawsze takie same
PlatformaStandardowe moduły biblioteczne spoza rdzeniaModuły i rozszerzenia Java
AplikacjaWłasne klasy Twojego programuŚcieżka klas (classpath) określona przez Twój program

Gdy ładowacz aplikacji jest proszony o klasę, zazwyczaj najpierw deleguje zapytanie wyżej. Pyta ładowacz platformy, który pyta ładowacz bootstrap, który próbuje ją załadować. Tylko wtedy, gdy żaden rodzic nie może jej załadować, dziecko próbuje. To jest model delegacji, który zapobiega dwukrotnemu ładowaniu tej samej klasy pod różnymi nazwami.

Tożsamość klasy i jak to działa

Klasa jest identyfikowana zarówno po jej nazwie jak i po ładowaczu, który ją zdefiniował. Dwa różne ładowacze mogą załadować klasę o nazwie com.example.MyClass, a JVM traktuje je jako dwa zupełnie różne typy. Brzmi to obscure, ale to podstawa wszystkiego ciekawego, co Java potrafi robić.

Bootstrap Loader: java.lang.Object (the core Object)
Application Loader: java.lang.Object (if somehow re-loaded by app loader)
→ Two different types, same name
→ This breaks your program, which is why delegation prevents it

Niestandardowe ładowacze klas

Programy mogą definiować własne ładowacze klas. Niestandardowy ładowacz może odczytywać bytecode z dowolnego miejsca: pliku JAR, sieci, bazy danych lub bytecode, który został wygenerowany lub przekształcony w locie. Dzieje się to w następujący sposób:

  • Systemy wtyczek ładują kod dynamicznie, nie wiedząc o nim w czasie kompilacji.
  • Serwery aplikacji (Tomcat, JBoss) uruchamiają wiele aplikacji w tej samej JVM, każda z własnym ładowaczem klas.
  • Mody Minecraft poprawiają klasy gry podczas ich ładowania.

Dlaczego to ma znaczenie dla modów Minecraft

Mody istnieją dzięki niestandardowemu ładowaniu klas. Ładowacz modów dla Minecrafta uruchamia grę za pomocą własnego, niestandardowego ładowacza klas, aby móc zobaczyć i modyfikować klasy gry podczas ich ładowania. To punkt przechwytywania, w którym dzieje się magia.

Kiedy ładowacz znajduje się pomiędzy dyskiem a JVM, może przekształcić klasę tuż przed zdefiniowaniem jej przez JVM:

Disk: obfuscated Minecraft class
    ↓
Mod Loader (custom classloader)
    ↓ [bytecode transformation happens here]
    ↓
JVM: defines the patched class
    ↓
Memory: game runs with mods active

Mod może przepisać metodę gry, wstrzyknąć nowe zachowanie, dodać pola, wszystko to bez dotykania pliku JAR gry na dysku. Klasa, którą ostatecznie uruchamia JVM, to ta, którą zmodyfikował framework moda. Dokładnie tak działają Fabric, Forge i inne ładowacze modów w Minecraft.

Ładowanie leniwe

Klasy nie są wszystkie ładowane podczas uruchamiania. JVM ładuje każdą klasę po raz pierwszy, gdy jest ona faktycznie używana. Nazywa się to ładowaniem leniwym:

Program starts → JVM doesn't load String class yet
User calls System.out.println() → String is needed → Loader loads it

Oznacza to, że ładowacz klas musi znaleźć i załadować tylko klasy, które faktycznie dotyka twój kod. Nieużywane klasy nigdy nie wchodzą do pamięci. Dlatego uruchomienie Minecrafta może trochę potrwać (mody dodają wiele klas), ale nie zamarza na zawsze.

FAQ