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.
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:
- Trova — Il loader individua il bytecode per la classe denominata (da un JAR, il disco, la rete o generato al volo)
- Verifica e collega — La JVM verifica che il bytecode sia valido e risolve i riferimenti ad altre classi
- 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:
| Caricatore | Caricamenti | Scopo |
|---|---|---|
| Bootstrap | Classi runtime Java di base | Garantisce che tipi fondamentali come java.lang.Object siano sempre gli stessi |
| Piattaforma | Moduli della libreria standard al di fuori del core | Moduli e estensioni Java |
| Applicazione | Classi proprie del tuo programma | Il 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
Al primo utilizzo, non all'avvio. La prima volta che il tuo codice interagisce con un tipo, la JVM richiede a un classloader di trovarlo e definirlo. Questo si chiama caricamento pigro, ed è il motivo per cui il codice inutilizzato non occupa mai memoria.
Un classloader chiede al suo genitore di caricare una classe prima di provarci da solo. La catena dei genitori arriva fino al classloader di bootstrap. Questo impedisce che la stessa classe venga caricata due volte con lo stesso nome da loader diversi.
Sì, se classloader diversi li definiscono. La JVM identifica una classe sia per nome che per il suo loader di definizione, quindi le classi con lo stesso nome provenienti da loader separati sono considerate tipi distinti.
Intercettare le classi durante il caricamento e trasformarle in memoria. Un loader personalizzato si interpone tra il disco e la JVM, modificando il bytecode prima che la JVM definisca la classe. È così che i mod patchano Minecraft senza modificare il file jar del gioco.