ProgrammationDéveloppeur Backend Java Senior

Comment fonctionne le mécanisme de chargement des classes (class loading) en Java, quels types de chargeurs de classes existent et quels problèmes peuvent survenir en cas de mauvaise utilisation ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Java, les classes sont chargées en mémoire par des objets spéciaux appelés chargeurs de classes (class loaders). Chaque chargeur de classes définit son propre espace de visibilité pour les classes.

Types de chargeurs de classes :

  1. Bootstrap ClassLoader — charge les classes de base JDK (rt.jar, etc.).
  2. Extension ClassLoader — charge les extensions à partir de $JAVA_HOME/lib/ext.
  3. System (Application) ClassLoader — charge les classes à partir du classpath de l'application.
  4. Custom ClassLoader — chargeurs personnalisés pour le chargement dynamique.

Caractéristiques :

  • Une classe est considérée comme unique dans le cadre de la combinaison (chargeur de classes, nom de classe).
  • Deux classes identiques, chargées par des chargeurs différents, sont considérées comme différentes.
  • Les chargeurs de classes forment une hiérarchie (parent-enfant).
  • Il est possible de charger des classes à partir de bytecode (defineClass) — pertinent pour les plugins/JSP, etc.

Question piège.

Peut-on charger plusieurs versions d'une même classe dans le processus JVM, et comment faire cela ?

Réponse : Oui, c'est possible si l'on utilise différents chargeurs de classes. Les classes ayant le même FQDN, chargées par des chargeurs différents, sont considérées comme différents types pour la JVM.

Exemple :

ClassLoader loader1 = new URLClassLoader(new URL[]{...}); ClassLoader loader2 = new URLClassLoader(new URL[]{...}); Class clazz1 = loader1.loadClass("com.example.MyClass"); Class clazz2 = loader2.loadClass("com.example.MyClass"); System.out.println(clazz1 == clazz2); // false

Exemples d'erreurs réelles dues à une méconnaissance des subtilités du sujet.


Histoire

Dans un grand système serveur, des plugins étaient utilisés, chacun étant connecté via un chargeur de classes personnalisé distinct. Les plugins échangeaient des objets via une interface commune, également chargée par son propre chargeur de classes. Une ClassCastException était levée lors des conversions de type, car l'interface 'PluginApi' du chargeur de classes du plugin et celle du système principal étaient considérées comme des types différents.


Histoire

Une tentative de "rechargement à chaud" d'un servlet via un chargeur de classes personnalisé a entraîné des fuites de mémoire — l'ancienne classe n'était pas déchargée de la mémoire, car il restait des références dans les variables statiques. En conséquence, PermGen était rapidement saturé.


Histoire

Un produit prenant en charge le chargement dynamique de modules utilisait implicitement le chargeur de classes système, mais les développeurs l'ont accidentellement remplacé par un parent, perdant ainsi l'accès aux classes de base (par exemple, de JDK). Cela s'est manifesté par des plantages, lorsque le nouveau module ne pouvait pas charger les classes Java standard, par exemple, java.sql.Driver.