Learn · concepts

Cos'è un Classloader Java?

Un classloader individua e carica le classi Java nella JVM a runtime. Ecco come funzionano, perché la gerarchia è importante e perché i mod dipendono da essi.

TRtrolPublished 5 min read

Un classloader è la parte del runtime Java che individua un file di classe e lo carica nella JVM. Ogni volta che il tuo codice interagisce con un tipo per la prima volta, la JVM chiede a un classloader: "Trova questa classe per me". Il loader individua il bytecode, lo legge e la JVM definisce la classe in memoria. Questo meccanismo è invisibile nei normali programmi Java, ma è l'esatto punto di aggancio che i framework di mod utilizzano per patchare Minecraft.

Cosa fa effettivamente un classloader

Un classloader trasforma un nome di classe in una classe utilizzabile all'interno della JVM in esecuzione. Richiedi un tipo, il loader trova il file .class corrispondente (o lo genera), lo verifica e lo collega, e la classe è pronta per l'uso.

Il lavoro avviene in tre passaggi:

  1. Trova — Il loader individua il bytecode per la classe denominata (da un JAR, il disco, la rete o generato al volo)
  2. Verifica e collega — La JVM verifica che il bytecode sia valido e risolve i riferimenti ad altre classi
  3. Usa — La classe è pronta e il tuo programma può istanziarla

Questo è il motivo per cui i programmi Java possono caricare dinamicamente codice a runtime che non è mai stato menzionato a tempo di compilazione. Il nome della classe è sufficiente per il loader per andare a trovarla.

La gerarchia dei classloader

I classloader sono organizzati in una catena genitore, e la maggior parte di essi chiede prima al proprio genitore. Un'applicazione Java standard ha tre classloader integrati:

CaricatoreCaricamentiScopo
BootstrapClassi runtime Java di baseGarantisce che tipi fondamentali come java.lang.Object siano sempre gli stessi
PiattaformaModuli della libreria standard al di fuori del coreModuli e estensioni Java
ApplicazioneClassi proprie del tuo programmaIl classpath specificato dal tuo programma

Quando al caricatore dell'applicazione viene richiesto una classe, di solito delega prima verso l'alto. Chiede al caricatore della piattaforma, che chiede al caricatore bootstrap, che tenta di caricarla. Solo se nessun genitore può caricarla, il figlio ci prova. Questo è il modello di delega, e impedisce che la stessa classe venga caricata due volte con nomi diversi.

Identità delle classi e come funziona

Una classe è identificata sia dal suo nome che dal caricatore che l'ha definita. Due caricatori diversi possono caricare una classe chiamata com.example.MyClass, e la JVM le considera due tipi completamente diversi. Questo può sembrare oscuro, ma è il fondamento di tutto ciò che di interessante può fare Java.

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

Caricatori di classi personalizzati

I programmi possono definire i propri caricatori di classi. Un caricatore personalizzato può leggere bytecode da qualsiasi luogo: un file JAR, la rete, un database o bytecode che ha generato o trasformato al volo. Funziona così:

  • I sistemi di plugin caricano codice dinamicamente senza conoscerlo al momento della compilazione.
  • I server applicativi (Tomcat, JBoss) eseguono più applicazioni nella stessa JVM, ciascuna con il proprio classloader.
  • Le mod di Minecraft patchano le classi di gioco durante il loro caricamento.

Perché questo è importante per le mod di Minecraft

Le mod esistono grazie al caricamento di classi personalizzato. Un mod loader per Minecraft avvia il gioco attraverso il proprio classloader personalizzato in modo da poter vedere e modificare le classi di gioco durante il loro caricamento. È in quel punto di intercettazione che avviene la magia.

Quando un loader si interpone tra il disco e la JVM, può trasformare una classe proprio prima che la JVM la definisca:

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

Una mod può riscrivere un metodo di gioco, iniettare nuovi comportamenti, aggiungere campi, senza toccare il JAR di gioco sul disco. La classe che la JVM esegue è quella che il framework della mod ha modificato. È esattamente così che Fabric, Forge e altri mod loader patchano Minecraft.

Lazy loading

Le classi non vengono caricate all'avvio. La JVM carica ogni classe la prima volta che viene effettivamente utilizzata. Questo viene chiamato lazy loading:

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

Questo significa che un classloader deve trovare e caricare solo le classi che il tuo codice tocca. Le classi inutilizzate non entrano mai in memoria. Questo è il motivo per cui l'avvio di Minecraft può richiedere un po' di tempo (le mod aggiungono molte classi) ma non si blocca per sempre.

FAQ