Chargeur de classe
Il est utilisé pour fournir du bytecode compilé à la JVM, qui est généralement stocké dans des fichiers avec l'extension.class
, mais peut également être obtenu à partir d'autres sources, par exemple téléchargé sur le réseau ou généré par l'application elle-même. Selon la spécification Java SE, pour que le code s'exécute dans la JVM, vous devez effectuer trois étapes :
-
charger le bytecode à partir des ressources et créer une instance de la classe
Class
Cela inclut la recherche de la classe demandée parmi celles chargées précédemment, l'obtention du bytecode pour le chargement et la vérification de son exactitude, la création d'une instance de la classe
Class
(pour travailler avec elle au moment de l'exécution) et le chargement des classes parentes. Si les classes parentes et les interfaces n'ont pas été chargées, alors la classe en question est considérée comme non chargée. -
liaison (ou liaison)
Selon le cahier des charges, cette étape est divisée en trois étapes supplémentaires :
- Vérification , l'exactitude du bytecode reçu est vérifiée.
- Préparation , allouant de la RAM aux champs statiques et les initialisant avec des valeurs par défaut (dans ce cas, l'initialisation explicite, le cas échéant, se produit déjà au stade de l'initialisation).
- Résolution , résolution de liens symboliques de types, champs et méthodes.
-
initialiser l'objet reçu
ici, contrairement aux paragraphes précédents, tout semble clair sur ce qui devrait se passer. Il serait bien sûr intéressant de comprendre exactement comment cela se produit.
- La classe doit être entièrement chargée avant d'être liée.
- Une classe doit être entièrement testée et préparée avant d’être initialisée.
- Des erreurs de résolution de lien se produisent lors de l'exécution du programme, même si elles ont été détectées au stade de la liaison.
Types de chargeurs Java
Il existe trois chargeurs standards en Java, chacun chargeant une classe à partir d'un emplacement spécifique :-
Bootstrap est un chargeur de base, également appelé Primordial ClassLoader.
charge les classes JDK standard à partir de l'archive rt.jar
-
Extension ClassLoader – chargeur d'extensions.
charge les classes d'extension, qui se trouvent par défaut dans le répertoire jre/lib/ext, mais peuvent être définies par la propriété système java.ext.dirs
-
System ClassLoader – chargeur système.
charge les classes d'application définies dans la variable d'environnement CLASSPATH
ClassLoader de classe abstraite
Chaque chargeur, à l'exception de celui de base, est un descendant de la classe abstraitejava.lang.ClassLoader
. Par exemple, l'implémentation du chargeur d'extension est la classe sun.misc.Launcher$ExtClassLoader
, et le chargeur système est sun.misc.Launcher$AppClassLoader
. Le chargeur de base est natif et son implémentation est incluse dans la JVM. Toute classe qui s'étend java.lang.ClassLoader
peut fournir sa propre façon de charger des classes avec du blackjack et ces mêmes. Pour ce faire, il est nécessaire de redéfinir les méthodes correspondantes, que je ne peux pour l'instant considérer que superficiellement, car Je n'ai pas compris ce problème en détail. Les voici:
package java.lang;
public abstract class ClassLoader {
public Class<?> loadClass(String name);
protected Class<?> loadClass(String name, boolean resolve);
protected final Class<?> findLoadedClass(String name);
public final ClassLoader getParent();
protected Class<?> findClass(String name);
protected final void resolveClass(Class<?> c);
}
loadClass(String name)
l'une des rares méthodes publiques qui constitue le point d'entrée pour le chargement des classes. Son implémentation se résume à appeler une autre méthode protégée loadClass(String name, boolean resolve)
, qui doit être remplacée. Si vous regardez la Javadoc de cette méthode protégée, vous pouvez comprendre quelque chose comme ceci : deux paramètres sont fournis en entrée. L'un est le nom binaire de la classe (ou le nom de classe complet) qui doit être chargé. Le nom de la classe est spécifié avec une liste de tous les packages. Le deuxième paramètre est un indicateur qui détermine si une résolution de lien symbolique est requise. Par défaut, c'est false , ce qui signifie que le chargement paresseux des classes est utilisé. Ensuite, selon la documentation, dans l'implémentation par défaut de la méthode, il y a un appel findLoadedClass(String name)
qui vérifie si la classe a déjà été chargée précédemment et, si c'est le cas, renvoie une référence à cette classe. Sinon, la méthode de chargement de classe du chargeur parent sera appelée. Si aucun des chargeurs ne parvient à trouver une classe chargée, chacun d'eux, dans l'ordre inverse, tentera de trouver et de charger cette classe, en remplaçant le fichier findClass(String name)
. Ceci sera discuté plus en détail dans le chapitre « Schéma de chargement de classe ». Et enfin, enfin et surtout, une fois la classe chargée, en fonction de l' indicateur de résolution , il sera décidé si les classes doivent être chargées via des liens symboliques. Un exemple clair est que l' étape Résolution peut être appelée pendant l'étape de chargement de la classe. Par conséquent, en étendant la classe ClassLoader
et en remplaçant ses méthodes, le chargeur personnalisé peut implémenter sa propre logique pour transmettre le bytecode à la machine virtuelle. Java prend également en charge le concept de chargeur de classe « actuel ». Le chargeur actuel est celui qui a chargé la classe en cours d'exécution. Chaque classe sait avec quel chargeur elle a été chargée, et vous pouvez obtenir cette information en appelant son String.class.getClassLoader()
. Pour toutes les classes d'applications, le chargeur "actuel" est généralement celui du système.
Trois principes de chargement de classe
-
Délégation
La demande de chargement de la classe est transmise au chargeur parent et une tentative de chargement de la classe elle-même n'est effectuée que si le chargeur parent n'a pas pu trouver et charger la classe. Cette approche vous permet de charger des classes avec le chargeur le plus proche possible de celui de base. Cela permet d’obtenir une visibilité maximale de la classe. Chaque chargeur conserve un enregistrement des classes qu'il a chargées et les place dans son cache. L’ensemble de ces classes est appelé portée.
-
Visibilité
Le chargeur ne voit que « ses » classes et les classes « parent » et n'a aucune idée des classes qui ont été chargées par son « enfant ».
-
Unicité
Une classe ne peut être chargée qu'une seule fois. Le mécanisme de délégation garantit que le chargeur qui lance le chargement de la classe ne surcharge pas une classe précédemment chargée dans la JVM.
Schéma de chargement de classe
Lorsqu'un appel pour charger une classe se produit, cette classe est recherchée dans le cache des classes déjà chargées du chargeur actuel. Si la classe souhaitée n'a pas été chargée auparavant, le principe de délégation transfère le contrôle au chargeur parent, qui se situe un niveau plus haut dans la hiérarchie. Le chargeur parent essaie également de trouver la classe souhaitée dans son cache. Si la classe a déjà été chargée et que le chargeur connaît son emplacement, alors un objetClass
de cette classe sera renvoyé. Sinon, la recherche se poursuivra jusqu'à ce qu'elle atteigne le chargeur de démarrage de base. Si le chargeur de base ne dispose pas d'informations sur la classe requise (c'est-à-dire qu'elle n'a pas encore été chargée), le bytecode de cette classe sera recherché à l'emplacement des classes que le chargeur donné connaît, et si la classe ne peut pas être chargé, le contrôle reviendra au chargeur enfant, qui tentera de charger à partir de sources qu'il connaît. Comme mentionné ci-dessus, l'emplacement des classes pour le chargeur de base est la bibliothèque rt.jar, pour le chargeur d'extensions - le répertoire avec les extensions jre/lib/ext, pour celui du système - CLASSPATH, pour celui de l'utilisateur, cela peut être quelque chose de différent . Ainsi, la progression du chargement des classes va dans la direction opposée - du chargeur racine au chargeur actuel. Lorsque le bytecode de la classe est trouvé, la classe est chargée dans la JVM et une instance du type est obtenue Class
. Comme vous pouvez facilement le voir, le schéma de chargement décrit est similaire à l'implémentation de la méthode ci - dessus loadClass(String name)
. Ci-dessous, vous pouvez voir ce diagramme dans le diagramme.
Comme conclusion
Dans les premières étapes de l'apprentissage d'une langue, il n'est pas particulièrement nécessaire de comprendre comment les classes sont chargées en Java, mais connaître ces principes de base vous aidera à éviter le désespoir lorsque vous rencontrerez des erreurs telles queClassNotFoundException
ou NoClassDefFoundError
. Eh bien, ou du moins, comprenez approximativement quelle est la racine du problème. Ainsi, une exception ClassNotFoundException
se produit lorsqu'une classe est chargée dynamiquement pendant l'exécution du programme, lorsque les chargeurs ne peuvent trouver la classe requise ni dans le cache, ni le long du chemin de classe. Mais l'erreur NoClassDefFoundError
est plus critique et se produit lorsque la classe requise était disponible lors de la compilation, mais n'était pas visible lors de l'exécution du programme. Cela peut se produire si le programme a oublié d'inclure la bibliothèque qu'il utilise. Eh bien, le fait même de comprendre les principes de la structure de l'outil que vous utilisez dans votre travail (pas nécessairement une immersion claire et détaillée dans ses profondeurs) ajoute une certaine clarté à la compréhension des processus qui se déroulent à l'intérieur de ce mécanisme, qui, en à son tour, conduit à une utilisation confiante de cet outil.
GO TO FULL VERSION