Processus de chargement de classe
Le travail principal d'un chargeur de classe est de charger des fichiers de classe dans le JVM. Comme indiqué dans la figure ci-dessous, le processus est divisé en trois étapes:
1. Chargement: Localisez le fichier de classe à charger et chargez son flux d'octets dans le JVM;
2. Lien: allouer la structure de mémoire la plus élémentaire à charger pour enregistrer ses informations, telles que les propriétés, les méthodes et les classes référencées. À ce stade, la classe n'est toujours pas disponible;
(1) Vérification: Vérifiez le flux d'octets chargé, tel que le format et la sécurité;
(2) Attribution de la mémoire: préparer un espace mémoire pour cette classe pour représenter ses propriétés, ses méthodes et ses classes référencées;
(3) Analyse: Chargez d'autres classes référencées par cette classe, telles que la classe parent, les interfaces implémentées, etc.
3. Initialisation: attribuez des valeurs aux variables de classe.
Le niveau de chargeur de classe
La ligne pointillée de la figure ci-dessous est plusieurs chargeurs de classe importants fournis par JDK, et la description détaillée est la suivante:
(1) chargeur de classe bootstrap: lors du démarrage d'une classe contenant la fonction principale, chargez le package JAR dans le répertoire Java_Home / Lib ou -xbootClassPath spécifié répertoire;
(2) Chargeur de classe d'extension: Chargez le package JAR dans le répertoire Java_Home / Lib / EXT ou -djava.ext.DIRS Répertoire spécifié.
(3) Chargeur de classe System: Chargez ClassPath ou -djava.class.Path Classe ou Package JAR dans le répertoire spécifié.
Ce que vous devez savoir, c'est:
1. En plus du chargeur de classe bootstrap, d'autres chargeurs de classe sont des sous-classes de la classe java.lang.classloader;
2. Le chargeur de classe bootstrap n'est pas implémenté en Java. Si vous n'utilisez pas de chargeur de classe personnalisé, alors java.lang.string.class.getClassloader () est nul et le chargeur parent du chargeur de classe d'extension est également nul;
3. Plusieurs façons d'obtenir des chargeurs de classe:
(1) Obtenir un chargeur de classe bootstrap: ce que vous obtenez doit être nul lorsque vous essayez d'obtenir un chargeur de classe bootstrap. Vous pouvez le vérifier de la manière suivante: Utilisez la méthode GetClassLoader des objets de classe dans le package RT.JAR, telles que java.lang.string.class.getClassLoader () pour obtenir ou obtenir le chargeur de classe d'extension, puis appeler la méthode Getparent à obtenir;
(2) Obtenez le chargeur de classe d'extension: utilisez la méthode GetClassLoader de l'objet de classe dans le package JAR dans le répertoire Java_Home / Lib / EXT ou obtenez d'abord le chargeur de classe système, puis obtenez-le via sa méthode GetParent;
(3) Obtenez un chargeur de classe système: appelez la méthode GetClassLoader de l'objet de classe contenant la fonction principale, ou appelez thread.currentThread (). GetContextClassLoader () ou appel classloader.getSystemClassLoader () dans la fonction principale;
(4) Obtenez un chargeur de classe défini par l'utilisateur: appelez la méthode GetClassloader de l'objet de classe ou appelez thread.currentThread (). GetContextClassloader ();
Principes opérationnels du chargeur de classe
1. Principe d'agent
2. Principe de visibilité
3. Le principe de l'unicité
4. Principe proxy Le principe proxy fait référence à un chargeur de classe demandant à son proxy de chargeur parent de charger lors du chargement d'une classe, et le chargeur parent demandera également à son proxy de chargeur parent à charger, comme indiqué sur la figure ci-dessous.
Pourquoi utiliser le mode proxy? Tout d'abord, cela peut réduire le chargement en double d'une classe. (Y a-t-il une autre raison?)
Où mal comprendre:
Généralement, on pense que l'ordre proxy du chargeur de classe est d'abord parent, c'est-à-dire:
1. Lors du chargement d'une classe, le chargeur de classe vérifie d'abord s'il a chargé la classe. S'il a été chargé, il reviendra; Sinon, demandez au chargeur parent de proxy;
2. Le chargeur parent répète le fonctionnement de 1 jusqu'à la charge de classe bootstrap;
3. Si le chargeur de classe bootstrap ne charge pas la classe, il essaiera de le charger, et s'il réussit, il reviendra; S'il échoue, une classe ClassNotFoundException est lancée, elle sera chargée par le chargeur pour enfants;
4. Le chargeur de sous-classe attrape l'exception et essaie de le charger. S'il réussit, il revient. S'il échoue, il lance une classe ClassNotFoundException jusqu'à ce que le chargeur de sous-classe chargé soit initié.
Cette compréhension est correcte pour les chargeurs tels que le chargeur de classe bootstrap, le chargeur de classe d'extension et le chargeur de classe système, mais certains chargeurs personnalisés ne sont pas le cas. Par exemple, certains chargeurs de classe implémentés par IBM Web Sphere Portal Server sont Parent Last, qui est le chargeur pour enfants qui tente de charger d'abord. Si la charge échoue, le chargeur parent sera demandé. La raison en est: si vous vous attendez à ce qu'une certaine version de Log4j soit utilisée par toutes les applications, mettez-la dans la bibliothèque WAS_HOME, et elle sera chargée quand il a été le début. Si une application souhaite utiliser une autre version de Log4j, elle ne peut pas être réalisée si elle utilise d'abord Parent car la classe de Log4j est déjà chargée dans le chargeur parent. Cependant, si le parent est utilisé, le chargeur de classe responsable du chargement de l'application privilégiera le chargement d'une autre version de Log4j.
Principe de visibilité
La visibilité de chaque classe au chargeur de classe est différente, comme le montre la figure ci-dessous.
Pour étendre les connaissances, OSGI profite de cette fonctionnalité. Chaque bundle est chargé par un chargeur de classe séparé, de sorte que chaque chargeur de classe peut charger une version d'une certaine classe, de sorte que l'ensemble du système peut utiliser plusieurs versions d'une classe.
Le principe de l'unicité
Chaque classe est chargée au maximum une fois dans un chargeur.
Connaissance étendue 1: Pour être précis, le modèle singleton fait référence à un objet singleton selon lequel une seule copie d'une classe dans un ensemble de chargeurs de classe.
Connaissances étendues 2: Une classe peut être chargée par plusieurs chargeurs de classe. Chaque objet de classe est dans son propre espace de noms. Lorsque vous comparez l'objet de classe ou le type de conversion de l'instance, il comparera son espace de noms respectif en même temps, tel que:
La classe Klass est chargée par ClassloadaRa, en supposant que l'objet de classe est Klassa; Il est chargé par ClassloaderB, en supposant que l'objet de classe est KLASSB, alors Klassa n'est pas égal à Klassb. Dans le même temps, lorsque l'instance de Classa est jetée à Klassb, une exception ClassCastException sera lancée.
Pourquoi avez-vous besoin d'un chargeur de classe personnalisé? Le chargeur de classe personnalisé ajoute beaucoup de flexibilité à la langue java. Les principales utilisations sont:
1. Les classes peuvent être chargées à partir de plusieurs endroits, comme sur le réseau, dans la base de données, ou même des fichiers source instantanément compilés;
2. Après la personnalisation, le chargeur de classe peut charger une certaine version du fichier de classe en principe au moment de l'exécution;
3. Après la personnalisation, le chargeur de classe peut décharger dynamiquement certaines classes;
4. Après personnalisation, le chargeur de classe peut décrypter et décompresser la classe avant de charger la classe.
Chargement implicite et explicite des classes
Charge implicite: Lorsqu'une classe est référencée, héritée ou instanciée, elle sera chargée implicitement. Si la charge échoue, NoclassdeffoundError est lancé.
Chargement explicite: utilisez la méthode suivante, si la charge échoue, une classe ClassNotFoundException sera lancée.
cl.loadClass (), CL est une instance du chargeur de classe;
Class.forname (), charge à l'aide du chargeur de classe de la classe actuelle.
Si l'exécution du bloc statique de la classe a les classes suivantes:
Package CN.Fengd; classe publique Dummy {static {System.out.println ("hi"); }} Créer une autre classe de test:
Package CN.Fengd; classe publique classloaderTest {public static void main (String [] args) lève InstantiationException, exception {try {/ * * différentes façons de charger. * / Class c = classloaderTest.class.getClassloadher (). LoadClass ("cn.fengd.dummy"); } catch (exception e) {// TODO Bloc de capture généré automatiquement e.printStackTrace (); }}}Dans quelle mesure est-il efficace après la course?
Et s'il est remplacé par class.forname ("cn.fengd.dummy"); ou nouveau mannequin ()?
Salut sera sorti.
Questions fréquemment posées:
1. Le type spécifié est-il chargé de chargeurs de classe différents le même type?
Dans Java, une classe utilise son nom de classe entièrement qualifié comme identifiant. Le nom de classe de correspondance exacte mentionné ici inclut le nom du package et le nom de classe. Cependant, dans le JVM, une classe utilise son nom complet et une instance du chargeur de classe de chargement comme identificateurs uniques. Les classes chargées de différents chargeurs de classe seront placées dans différents espaces de noms. Nous pouvons utiliser deux chargeurs de classe personnalisés pour charger un type personnalisé (notez qui ne placent pas le bytecode du type personnalisé dans le chemin du système ou le chemin d'extension du système, sinon il sera chargé d'abord par le chargeur de classe système ou le chargeur de classe d'extension), puis utiliser les deux instances de classe obtenues pour obtenir des jugements Java.lang.object.equals (…), et vous obtiendrez des résultats inégaux. Cela peut être fait en écrivant deux chargeurs de classe personnalisés pour charger le même type personnalisé, puis en juger un jugement; En même temps, vous pouvez tester le chargement de Java. * Type, puis comparer et tester les résultats du test.
2. Appelez directement la méthode class.forname (nom de chaîne) dans le code. Quel chargeur de classe déclenchera le comportement de chargement de classe?
Class.forname (nom de chaîne) utilisera le chargeur de classe qui appelle la classe pour charger la classe par défaut. Analysons directement le code JDK correspondant:
//java.lang.class.java publicStatic class <?> forname (String classname) lève classnotfoundException {return forname0 (className, true, classloader.getCallerderSloadher ());} // java.lang.classloadher.java// renvoie le chargeur de classe de l'invokor, ou null not getCallerClassLoader () {// Obtenez le type de classe d'appel (appelant) CLASSE CALLER = Reflection.getCallerClass (3); // Cela peut être null si la machine virtuelle le demande si (appelant == null) {returnNull; } // Appelez la méthode locale dans java.lang.class pour obtenir le Classloader qui charge la classe d'appel (appelant) return caller.getClassloader0 ();} // java.lang.class.java//native implémentation de la machine virtuelle pour obtenir le chargeur de classe de la classe de classe actuelle Native ClassLoader GetClassoller0 (); 3. Lors de l'écriture d'un chargeur de classe personnalisé, si le chargeur parent n'est pas défini, alors quel est le chargeur parent?
Lorsque le chargeur de classe parent n'est pas spécifié, le chargeur de classe système est utilisé par défaut. Certaines personnes peuvent ne pas comprendre, jetons maintenant un coup d'œil à l'implémentation de code correspondante de JDK. Comme nous le savons tous, nous écrivons un chargeur de classe personnalisé directement ou indirectement de la classe abstraite java.lang.classloader. Le constructeur par défaut correspondant sans paramètres est implémenté comme suit:
// Extrait de java.lang.classloader.javaprotected classloader () {SecurityManager Security = System.getSecurityManager (); if (Security! = null) {Security.CheckCreateClassloader (); } this.parent = getSystemClassloadher (); initialisé = true;}Jetons un coup d'œil à l'implémentation correspondante de la méthode GetSystemClassloader ():
privatestaticsynchronizedVoid initsystemClassloader () {// ... Sun.Misc.launcher l = Sun.Misc.launcher.getlauncher (); scl = l.getClassloader (); // ...}Nous pouvons écrire un code de test simple pour le tester:
System.out.println (Sun.Misc.Launcher.GetLauncher (). GetClassOader ());
La sortie correspondante de cette machine est la suivante:
Sun.misc.Launcher$AppCLASSOLODER@197D257
Ainsi, nous pouvons maintenant croire que lorsque le chargeur de classe personnalisé ne spécifie pas le chargeur de classe parent, le chargeur de classe parent par défaut est le chargeur de classe système. En même temps, nous pouvons tirer les conclusions suivantes:
Le chargeur de classe défini par l'utilisateur instantané ne spécifie pas le chargeur de classe parent, puis les classes aux trois endroits suivantes peuvent également être chargées:
(1) Classes sous <java_runtime_home> / lib
(2) Classes à l'emplacement spécifié par la variable système java.ext.dir
(3) Classes sous le chemin de la classe de projet actuel ou dans l'emplacement spécifié par la variable système java.class.path
4. Lors de l'écriture d'un chargeur de classe personnalisé, que se passera-t-il si le chargeur de classe parent est obligé d'être nul? Si le chargeur de classe personnalisé ne peut pas charger la classe spécifiée, va-t-elle définitivement échouer?
La spécification JVM stipule que si le chargeur de classe défini par l'utilisateur force le chargeur de classe parent à Null, le chargeur de classe de démarrage sera automatiquement défini sur le chargeur de classe parent du chargeur de classe défini par l'utilisateur actuel (ce problème a déjà été analysé). En même temps, nous pouvons tirer les conclusions suivantes:
Si le chargeur de classe défini par l'utilisateur instantané ne spécifie pas le chargeur de classe parent, il peut également charger la classe sous <java_runtime_home> / lib, mais que la classe sous <java_runtime_home> / lib / extréitaire ext ne peut pas être chargée à ce moment.
Remarque: Les conclusions inférées des questions 3 et 4 sont basées sur le chargeur de classe défini par l'utilisateur lui-même qui poursuit la logique de délégation par défaut de java.lang.classloader.loadClass (…). Si l'utilisateur modifie cette logique de délégation par défaut, la conclusion inférée ci-dessus peut ne pas être valide. Voir la question 5 pour plus de détails.
5. Quels sont les points d'attention généraux lors de la rédaction de chargeurs de classe personnalisés?
(1) Généralement, essayez de ne pas remplacer la logique de délégation dans la méthode de charge (…) existante. Généralement, cela se fait dans les versions avant JDK 1.2, et il s'avère que cela est très susceptible de faire en sorte que le chargeur de classe par défaut du système ne fonctionne pas correctement. Dans la spécification JVM et la documentation JDK (1.2 ou versions ultérieures), il n'est pas recommandé que les utilisateurs écrasent la méthode de charge (…). En revanche, les développeurs sont clairement rappelés d'écraser la logique FindClass (…) lors du développement d'un chargeur de classe personnalisé. Prenez un exemple pour vérifier le problème:
// User Class Class chargeer tortClassloadher.java (écraser Logic LoadClass) publicClassWrongClassloadExtendStend Classloader {public class <?> LoadClass (String Name) lève ClassNotFoundException {returnThis.FindClass (name); } Class protégé <?> findClass (nom de chaîne) lève classnotfoundException {// Supposons ici est juste à un répertoire spécifique autre que le projet d: / bibliothèque pour charger le code d'implémentation spécifique omis}}}Grâce à l'analyse précédente, nous savons déjà que la valeur par défaut du chargeur de classe définie par l'utilisateur (malédiction)
Le chargeur de classe reconnu est un chargeur de classe système, mais les conclusions des quatre types de problèmes ne sont pas valides maintenant. Tout le monde peut
Il suffit de le tester, maintenant <java_runtime_home> / lib, <java_runtime_home> / lib / ext et
Les classes sur le chemin de la classe de programme ne peuvent pas être chargées.
Question 5 Code de test 1
publicClass tortClassloadETTest {publicStaticVoid main (String [] args) {try {tortclassloader loader = new tailclassloader (); Class classloaded = loder.loadClass ("beans.account"); System.out.println (classloaded.getName ()); System.out.println (classloaded.getClassloader ()); } catch (exception e) {e.printStackTrace (); }}}(Remarque: D: Compte "Classes" Beans ".Classe existe physiquement)
Résultat de sortie:
java.io.FilenotFoundException: D: "Classes" Java "Lang" Object.class (Le système ne peut pas trouver le chemin spécifié.) sur java.io.fileInputStream.open (méthode native) sur java.io.fileinputStream. sur neftclassloadher.loadclass (neftclassloadher.java:29) sur java.lang.classloader.loadclassinternal (classloader.java:319) sur java.lang.classloader.defineclass1 (ClassLoader.java:620) at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at a java.lang.classloader.defineclass (classloader.java:400) sur tortclassloader.findClass (tortclassloader.java:43) sur neftclassloadher.loadclass (tortclassloader.java:29) à neftclassLoader.main (neftclassloderTest.java:27) Exception dans l'exception de thread "main" java.lang.noclassdeffounderror: java / lang / objet sur java.lang.classloader.defineclass1 (méthode native) sur java.lang.classloader.defineclass (classloader.java:620) sur java.lang.classer. Tortclassloader.findClass (tortclassloadher.java:43) sur neftclassloadher.loadclass (neftclassloader.java:29) sur neftclassloaderTest.main (tortclassloadert.java:27)
Cela signifie que même le supertype java.lang.object du type à charger ne peut pas être chargé. Les erreurs logiques répertoriées ici causées par l'écrasement de la classe de charge (…) sont évidemment relativement simples, et les erreurs logiques causées en fait peuvent être beaucoup plus compliquées.
Question 5 Test 2
// User Class Class chargeer tortclassloadher.java (ne pas écraser la logique de chargement de charge) publicClasswrongClassloadExtends classloader {protected class <?> FindClass (nom de chaîne) lève classNotFoundException {// Supposons ici est juste à un répertoire spécifique autre que le projet d: / library pour charger le code d'implémentation spécifique de la classe OMET}}}Après avoir modifié le code de chargeur de classe personnalisé, il est le cas suivant:
beans.accountwrongclassloader@1c78e57
Cela montre que Beans.Account est chargé avec succès et est chargé par le chargeur de classe personnalisé BrotClassloadher.
Je ne pense pas qu'il soit nécessaire d'expliquer les raisons à cela, et vous devriez être en mesure de l'analyser.
(2) Configurez correctement le chargeur de classe parent par l'analyse de la question 4 et de la question 5 ci-dessus. Je pense personnellement que c'est le point le plus important lors de la personnalisation des chargeurs de classe d'utilisateurs, mais il est souvent ignoré ou facilement apporté. Avec l'analyse du code JDK précédent comme base, je pense que tout le monde peut donner des exemples maintenant.
(3) Assurez-vous l'exactitude logique de la méthode FindClass (String), essayez de comprendre avec précision les tâches de chargement à définir par le chargeur de classe à définir, et garantir que le contenu de code de code correspondant peut être obtenu dans la plus grande mesure.
6. Comment déterminer les chemins que le chargeur de classe système peut charger?
Tout d'abord, vous pouvez appeler directement ClassLoader.getSystemClassloader () ou d'autres méthodes pour obtenir le chargeur de classe système (le chargeur de classe système et le chargeur de classe d'extension sont-ils dérivés de UrlClassLoader), et vous pouvez l'obtenir en appelant la méthode getUrls () dans URLClassLoader;
Deuxièmement, vous pouvez afficher directement les informations d'entrée sur le chemin de classe actuel en obtenant l'attribut système java.class.path. System.getProperty ("java.class.path")
7. Comment déterminer les chemins que le chargeur de classe d'extension standard peut charger?
Méthode un:
essayez {url [] excurls = ((urlclassloader) classloader.getSystemClassloader (). getParent ()). getUrls (); for (int i = 0; i <exturs.length; i ++) {System.out.println (excurls [i]); }} catch (exception e) {//…}La sortie correspondante de cette machine est la suivante:
fichier: / d: /demo/jdk1.5.0_09/jre/lib/ext/dnsns.jarfile: / d: /demo/jdk1.5.0_09/jre/lib/ext/localedata.jarfile : / D: /demo/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jarfile: / d: /demo/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar