Internes JVM, partie 1 - chargeur de classe

Une traduction de l'article a été préparée spécialement pour les étudiants du cours Java Developer .




Dans cette série d'articles, je parlerai du fonctionnement de la machine virtuelle Java. Aujourd'hui, nous examinons le mécanisme de chargement des classes dans la JVM .

La machine virtuelle Java est au cœur de l'écosystème technologique Java. Il permet aux programmes Java de mettre en œuvre le principe «écrire une fois exécuté partout». Comme les autres machines virtuelles, la JVM est un ordinateur abstrait. La tâche principale de la JVM est de charger les fichiers de classe et d'exécuter le bytecode qu'ils contiennent.

La JVM comprend divers composants, tels qu'un chargeur de classe , Garbage Collector (gestion automatique de la mémoire), un interpréteur, un compilateur JIT et des composants de contrôle de flux. Dans cet article, nous allons voir le chargeur de classe.

Le chargeur de classe charge les fichiers de classe pour votre application et l'API Java. Seuls les fichiers de classe d'API Java réellement nécessaires lors de l'exécution du programme sont chargés dans la machine virtuelle.

Le code d'octet est exécuté par le moteur d'exécution.



Qu'est-ce que le chargement de classe?


Le chargement de classe est la recherche et le chargement de types (classes et interfaces) dynamiquement pendant l'exécution du programme. Les données de type se trouvent dans des fichiers de classe binaire.

Étapes de chargement des classes


Le sous-système du chargeur de classe n'est pas seulement responsable de la recherche et de l'importation des données de classe binaire. Il effectue également la validation des classes importées, alloue et initialise la mémoire pour les variables de classe et aide à résoudre les liens symboliques. Ces actions sont effectuées dans l'ordre suivant:

  • Chargement - recherche et importation de données binaires pour un type par son nom, création d'une classe ou d'une interface à partir de cette représentation binaire.
  • Liaison (liaison) - vérification, préparation et, éventuellement, autorisation:
    • Vérification - vérification de l'exactitude du type importé.
    • Préparation - allouer de la mémoire pour les variables de classe statiques et initialiser la mémoire avec des valeurs par défaut.
    • Résolution - Convertissez les liens de type symbolique en liens directs.
  • L'initialisation est un appel au code Java qui initialise les variables de classe avec leurs valeurs initiales correctes.

Remarque - le chargeur de classe, en plus de charger des classes, est également responsable de la recherche de ressources. Une ressource est constituée de certaines données (par exemple, un fichier «.class», des données de configuration, des images) qui sont identifiées à l'aide d'un chemin abstrait séparé par un caractère «/». Les ressources sont généralement fournies avec une application ou une bibliothèque afin de pouvoir être utilisées dans le code d'application ou de bibliothèque.

Mécanisme de chargement de classe en Java


Note du traducteur - cette section décrit le comportement de java <9, dans java 9+ il y a eu de petits changements, qui sont décrits ci-dessous.

Java utilise le modèle de délégation de chargement de classe. L'idée de base est que chaque chargeur de classe a un chargeur «parent». Lorsqu'une classe est en cours de chargement , le chargeur «délègue» la recherche de classe à son parent avant de rechercher la classe seule.

Le modèle de délégation de chargeur de classe est un graphique de chargeurs qui se transmettent des demandes de chargement. La racine de ce graphique est le chargeur de démarrage bootstrap. Les chargeurs de classe sont créés avec un parent à qui ils peuvent déléguer la charge et rechercher la classe aux endroits suivants:

  • Cache
  • Le parent
  • Bootloader lui-même

Le chargeur de classe vérifie d'abord s'il a déjà chargé la classe. Si tel est le cas, la même classe renvoyée la dernière fois est renvoyée (la classe stockée dans le cache). Sinon, le parent a la possibilité de charger la classe. Ces deux étapes sont répétées de manière récursive en profondeur. Si le parent retourne null (ou lève une exception ClassNotFoundException ), le chargeur recherche la classe par lui-même.

La classe est chargée par le chargeur le plus proche de la racine, car le droit de charger d'abord la classe est toujours accordé au chargeur parent. Cela permet au chargeur de voir uniquement les classes chargées indépendamment, par son parent ou ses ancêtres. Il ne peut pas voir les classes chargées par les chargeurs enfants.

L'API Java SE Platform a historiquement défini deux chargeurs de classe:

Chargeur de classe de bootstrap (chargeur de base, primaire) - charge les classes à partir du chemin de classe de bootstrap.

Chargeur de classe système (chargeur parent ) - la classe parent pour les nouveaux chargeurs de classe et, en règle générale, le chargeur de classe utilisé pour charger et exécuter l'application.

Chargeurs de classe JDK 9+


Chargeur de classe d'application - couramment utilisé pour charger des classes d'application à partir d'un chemin de classe. Il s'agit également du chargeur de démarrage par défaut pour certains modules JDK qui contiennent des utilitaires ou exportent l'API des utilitaires. ( Note du traducteur: par exemple, jdk.jconsole , jdk.jshell , etc. )

Chargeur de classe de plate-forme - charge les modules Java SE et JDK sélectionnés (en fonction de la sécurité / des autorisations). Par exemple, java.sql.

Chargeur de classe Bootstrap - Charge les principaux modules Java SE et JDK.

Ces trois chargeurs de classe intégrés fonctionnent ensemble comme suit:

  • Le chargeur de classe d'application recherche d'abord les modules nommés définis pour tous les chargeurs intégrés. Si un module approprié est défini pour l'un de ces chargeurs, ce chargeur charge la classe. Si la classe n'est pas trouvée dans le module nommé défini pour l'un de ces chargeurs, le chargeur de classe d'application la délègue au parent. Si la classe n'est pas trouvée par le parent, le chargeur de classe d'application la recherche dans le chemin de classe. Les classes trouvées dans le chemin de classe sont chargées en tant que membres du module sans nom de ce chargeur.
  • Le chargeur de classe de plate-forme recherche les modules nommés définis pour tous les chargeurs intégrés. Si un module approprié est défini pour l'un de ces chargeurs, ce chargeur charge la classe. Si la classe n'est pas trouvée dans le module nommé défini pour l'un de ces chargeurs, le chargeur de classe de plateforme la délègue au parent.
  • Le chargeur de classe bootstrap recherche les modules nommés définis pour lui-même. Si la classe ne se trouve pas dans le module nommé défini pour le chargeur de démarrage bootstrap, le chargeur de démarrage bootstrap recherche les fichiers et répertoires ajoutés au chemin de classe bootstrap à l'aide du paramètre -Xbootclasspath / a (vous permet d'ajouter des fichiers et des répertoires au chemin de classe bootstrap). Les classes trouvées dans le chemin d'accès aux classes d'amorçage sont chargées en tant que membres du module sans nom de ce chargeur.

Pour afficher les chargeurs de classe intégrés, vous pouvez utiliser le code suivant:

 package ru.deft.homework; import java.sql.Date; public class BuiltInClassLoadersDemo { public static void main(String[] args) { BuiltInClassLoadersDemo demoObject = new BuiltInClassLoadersDemo(); ClassLoader applicationClassLoader = demoObject.getClass().getClassLoader(); printClassLoaderDetails(applicationClassLoader); // java.sql classes are loaded by platform classloader java.sql.Date now = new Date(System.currentTimeMillis()); ClassLoader platformClassLoder = now.getClass().getClassLoader(); printClassLoaderDetails(platformClassLoder); // java.lang classes are loaded by bootstrap classloader ClassLoader bootstrapClassLoder = args.getClass().getClassLoader(); printClassLoaderDetails(bootstrapClassLoder); } private static void printClassLoaderDetails(ClassLoader classLoader) { // bootstrap classloader is represented by null in JVM if (classLoader != null) { System.out.println("ClassLoader name : " + classLoader.getName()); System.out.println("ClassLoader class : " + classLoader.getClass().getName()); } else { System.out.println("Bootstrap classloader"); } } } 

En exécutant ce code sur Amazon Corretto 11.0.3 installé sur moi, nous obtenons le résultat suivant:

 ClassLoader name : app ClassLoader class : jdk.internal.loader.ClassLoaders$AppClassLoader ClassLoader name : platform ClassLoader class : jdk.internal.loader.ClassLoaders$PlatformClassLoader Bootstrap classloader 

Vous pouvez en savoir plus sur l'API ClassLoader ici (JDK 11) .

Source: https://habr.com/ru/post/fr468193/


All Articles