Learn · concepts

Qu'est-ce qu'un chargeur de classes Java ?

Un chargeur de classes trouve et charge les classes Java dans la JVM au moment de l'exécution. Voici comment ils fonctionnent, pourquoi la hiérarchie est importante et pourquoi les mods en dépendent.

TRtrolPublished 6 min read

Un chargeur de classes est la partie de l'environnement d'exécution Java qui trouve un fichier de classe et le charge dans la JVM. Chaque fois que votre code accède à un type pour la première fois, la JVM demande à un chargeur de classes : « Trouve cette classe pour moi. » Le chargeur localise le bytecode, le lit, et la JVM définit la classe en mémoire. Ce mécanisme est invisible dans les programmes Java normaux, mais c'est le crochet exact que les frameworks de mod utilisent pour modifier Minecraft.

Ce qu'un chargeur de classes fait réellement

Un chargeur de classes transforme un nom de classe en une classe utilisable à l'intérieur de la JVM en cours d'exécution. Vous demandez un type, le chargeur trouve le fichier .class correspondant (ou le génère), le vérifie et l'associe, et la classe est prête à être utilisée.

Le travail se déroule en trois étapes :

  1. Recherche — Le chargeur localise le bytecode pour la classe nommée (à partir d'un JAR, du disque, du réseau ou généré à la volée).
  2. Vérification et association — La JVM vérifie que le bytecode est valide et résout les références aux autres classes.
  3. Utilisation — La classe est prête et votre programme peut l'instancier.

C'est pourquoi les programmes Java peuvent charger dynamiquement du code au moment de l'exécution qui n'a jamais été mentionné au moment de la compilation. Le nom de la classe suffit pour que le chargeur puisse le trouver.

La hiérarchie des chargeurs de classes

Les chargeurs de classes sont organisés en une chaîne parentale, et la plupart d'entre eux demandent d'abord à leur parent. Une application Java standard possède trois chargeurs de classes intégrés :

ChargeurChargesBut
BootstrapClasses d'exécution Java de baseGarantit que les types de base tels que java.lang.Object sont toujours les mêmes
PlateformeModules de bibliothèque standard en dehors du noyauModules et extensions Java
ApplicationLes classes propres à votre programmeLe classpath que votre programme spécifie

Lorsque le chargeur d'application est interrogé pour une classe, il délègue normalement vers le haut en premier. Il demande au chargeur de plateforme, qui demande au chargeur bootstrap, qui tente de la charger. Ce n'est qu'à défaut de pouvoir la charger par un parent que l'enfant essaie. C'est le modèle de délégation, et il empêche la même classe d'être chargée deux fois sous des noms différents.

Identité des classes et son fonctionnement

Une classe est identifiée à la fois par son nom et par le chargeur qui l'a définie. Deux chargeurs différents peuvent chacun charger une classe appelée com.example.MyClass, et la JVM les considère comme deux types complètement différents. Cela peut sembler obscur, mais c'est le fondement de tout ce que Java peut faire d'intéressant.

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

Chargeurs de classes personnalisés

Les programmes peuvent définir leurs propres chargeurs de classes. Un chargeur personnalisé peut lire le bytecode à partir de n'importe où : un fichier JAR, le réseau, une base de données ou le bytecode qu'il a généré ou transformé à la volée. C'est ainsi :

  • Les systèmes de plugins chargent du code dynamiquement sans le connaître au moment de la compilation.
  • Les serveurs d'applications (Tomcat, JBoss) exécutent plusieurs applications dans la même JVM, chacune avec son propre chargeur de classes.
  • Les mods Minecraft modifient les classes du jeu au moment de leur chargement.

Pourquoi cela importe pour les mods Minecraft

Les mods existent grâce au chargement de classes personnalisé. Un chargeur de mods pour Minecraft lance le jeu via son propre chargeur de classes personnalisé afin de pouvoir voir et modifier les classes du jeu au moment de leur chargement. C'est à ce point d'interception que la magie opère.

Lorsqu'un chargeur se situe entre le disque et la JVM, il peut transformer une classe juste avant que la JVM ne la définisse :

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

Un mod peut réécrire une méthode du jeu, injecter un nouveau comportement, ajouter des champs, le tout sans toucher le fichier JAR du jeu sur le disque. La classe que la JVM exécute est celle que le framework de mod a modifiée. C'est exactement ainsi que Fabric, Forge et les autres chargeurs de mods patch Minecraft.

Chargement paresseux

Toutes les classes ne sont pas chargées au démarrage. La JVM charge chaque classe la première fois qu'elle est réellement utilisée. On appelle cela le chargement paresseux :

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

Cela signifie qu'un chargeur de classes n'a besoin de trouver et de charger que les classes que votre code touche réellement. Les classes inutilisées n'entrent jamais en mémoire. C'est pourquoi le démarrage de votre Minecraft peut prendre un certain temps (les mods ajoutent beaucoup de classes) mais ne se fige pas à jamais.

FAQ