Maison >Java >javaDidacticiel >Une analyse approfondie du chargeur de classe Class Loader en Java
Le processus de chargement de classe
La tâche principale du chargeur de classe est de charger les fichiers de classe dans la JVM. Comme le montre 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 la JVM
2. le fichier de classe à charger Une classe alloue la structure de mémoire la plus élémentaire pour contenir ses informations, telles que les propriétés, les méthodes et les classes référencées. A ce stade, la classe est toujours indisponible ;
(1) Vérification : vérifiez le flux d'octets chargé, tel que le format et la sécurité
(2) Allocation de mémoire : pour cette classe, préparez l'espace mémoire pour représenter ses attributs, méthodes et classes référencées ;
(3) Analyse : charger d'autres classes référencées par cette classe, telles que les classes parentes, les interfaces implémentées, etc.
3. Initialisation : attribuez des valeurs aux variables de classe.
Niveaux des chargeurs de classes
Au-dessus de la ligne pointillée dans la figure ci-dessous se trouvent plusieurs chargeurs de classes importants fournis par le JDK. La description détaillée est la suivante :
( 1) Bootstrap Class Loader : lors du démarrage d'une classe contenant la fonction principale, chargez le package jar dans le répertoire JAVA_HOME/lib ou le répertoire spécifié par -Xbootclasspath
(2) Extension Class Loader : chargez JAVA_HOME ; Répertoire /lib/ext ou Le package jar dans le répertoire spécifié par -Djava.ext.dirs.
(3) System Class Loader : chargez des classes ou des packages jar dans le répertoire spécifié par classpath ou -Djava.class.path.
Ce que vous devez savoir est :
1 À l'exception du chargeur de classe Bootstrap, les autres chargeurs de classe sont java.lang.ClassLoader Sous-classe de classe ; 🎜>2.Bootstrap Class Loader n'est pas implémenté en Java. Si vous n'utilisez pas de chargeur de classe personnalisé, alors java.lang.String.class.getClassLoader() sera nul et le parent d'Extension Class Loader est chargé. est également nul ;
3. Plusieurs façons d'obtenir le chargeur de classe :
(1) Obtenir le chargeur de classe Bootstrap : en essayant d'obtenir le chargeur de classe Bootstrap, le résultat doit être nul. Vous pouvez le vérifier de la manière suivante : utilisez la méthode getClassLoader de l'objet de classe dans le package rt.jar, telle que java.lang.String.class.getClassLoader() pour obtenir ou obtenir le chargeur de classe d'extension, puis appelez le Méthode getParent pour l'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 le 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 appelez ClassLoader.getSystemClassLoader() dans le main. function;
(4) Obtenir le chargeur de classe défini par l'utilisateur : appelez la méthode getClassLoader de l'objet de classe ou appelez Thread.currentThread().getContextClassLoader(); >
1. Principe du proxy
2. Principe de visibilité
4. Principe du proxy
Le principe du proxy signifie qu'un chargeur de classe demandera à son parent de charger une classe. Le chargeur parent demandera également à son agent de chargement parent de charger, comme le montre la figure ci-dessous.
Pourquoi utiliser le mode proxy ? Tout d’abord, cela peut réduire le chargement répété d’une classe. (Y a-t-il d'autres raisons ?)
Facilement mal compris :
On pense généralement que l'ordre proxy du chargeur de classe est Parent First, c'est-à-dire :
1. Lors du chargement d'une classe, le chargeur de classe vérifie d'abord s'il a chargé la classe, et si c'est le cas, retourne ; sinon, demande au chargeur parent de proxy
2. . Accédez à Bootstrap Class Loader ;
3. Si Bootstrap Class Loader ne charge pas la classe, il essaiera de la charger et reviendra si le chargement réussit, une exception ClassNotFoundException sera levée et l'enfant ; le chargeur le chargera ;
4. Sub Le chargeur de classe intercepte l'exception et essaie de la charger. S'il réussit, il renvoie une exception ClassNotFoundException jusqu'à ce que le chargeur de sous-classe lance le chargement.
Cette compréhension est correcte pour les chargeurs tels que Bootstrap Class Loader, Extension Class Loader et System Class Loader, mais certains chargeurs personnalisés ne le sont pas. Par exemple, certains chargeurs de classe implémentés par IBM Web Sphere Portal Server sont Parent Last, l'enfant. Le chargeur essaie de se charger en premier. Si le chargement échoue, le chargeur parent sera appelé. La raison en est la suivante : si vous vous attendez à ce qu'une certaine version de log4j soit utilisée par toutes les applications, placez-la dans la bibliothèque WAS_HOME, WAS est chargée. au démarrage. Si une application souhaite utiliser une autre version de log4j, cela ne peut pas être réalisé si Parent First est utilisé, car les classes de log4j ont déjà été chargées dans le chargeur parent. Mais si vous utilisez Parent Last, le chargeur de classe responsable du chargement de l'application chargera d'abord une autre version de log4j.
Principe de visibilité
La visibilité de chaque classe sur le chargeur de classe est différente, comme le montre la figure ci-dessous.
En élargissant les connaissances, OSGi profite de cette fonctionnalité. Chaque bundle est chargé par un chargeur de classe distinct, de sorte que chaque chargeur de classe peut charger une version d'une certaine classe, afin que l'ensemble du système puisse en utiliser une. Plusieurs versions d'une classe. classe.
<br/>
Principe d'unicité
Chaque classe peut être chargée au plus une fois dans un chargeur.
Connaissances étendues 1 : Pour être précis, le singleton auquel fait référence le modèle Singleton fait référence à une seule copie d'un objet d'une certaine classe dans un groupe de chargeurs de classe.
Connaissances étendues 2 : Une classe peut être chargée par plusieurs chargeurs de classe. Chaque objet de classe se trouve dans son propre espace de noms. Lors de la comparaison d'objets de classe ou de la conversion de type sur des instances, leurs noms respectifs seront comparés en même temps. . Space, par exemple :
La classe Klass est chargée par ClassLoaderA, en supposant que l'objet de classe est KlassA ; elle est également chargée par ClassLoaderB, en supposant que l'objet de classe est KlassB, alors KlassA n'est pas égal à KlassB. Dans le même temps, ClassCastException sera levée lorsqu'une instance de ClassA est convertie en KlassB.
Pourquoi un chargeur de classes personnalisé
Le chargeur de classes personnalisé ajoute beaucoup de flexibilité au langage Java. Ses principales utilisations sont :
1. base de données, ou même compiler le fichier source immédiatement pour obtenir le fichier de classe ;
2. Le chargeur de classe personnalisé peut en principe charger une certaine version du fichier de classe au moment de l'exécution
3. décharger certaines classes ;
4. Après la personnalisation, le chargeur de classe peut décrypter et décompresser la classe avant de charger la classe.
Chargement implicite et explicite des classes
Chargement implicite : Lorsqu'une classe est référencée, héritée ou instanciée, elle sera chargée implicitement. Si le chargement échoue, elle sera renvoyée NoClassDefFoundError.
Chargement explicite : utilisez la méthode suivante si le chargement échoue, une ClassNotFoundException sera levée.
cl.loadClass(), cl est une instance du chargeur de classe ;
Class.forName(), utilise le chargeur de classe de la classe actuelle à charger.
Exécution du bloc statique de classe
Supposons qu'il existe la classe suivante :
package cn.fengd; public class Dummy { static { System.out.println("Hi"); } }
Créez une autre classe de test :
package cn.fengd; public class ClassLoaderTest { public static void main(String[] args) throws InstantiationException, Exception { try { /* * Different ways of loading. */ Class c = ClassLoaderTest.class.getClassLoader().loadClass("cn.fengd.Dummy"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
Quel est l'effet après l'exécution ?
Salut ne sera pas émis. On peut voir que l'objet Class n'est pas initialisé après avoir utilisé loadClass ;
Si c.newInstance(); est ajouté après l'instruction Load, il y aura une sortie Hi et l'objet classe n'est initialisé que lorsque le la classe est instanciée.
Si vous modifiez l'instruction de chargement Class c = Class.forName("cn.fengd.Dummy", false, ClassLoader.getSystemClassLoader());
Hi ne sera pas affiché. Parce que le paramètre false signifie qu'il n'est pas nécessaire d'initialiser l'objet de cette classe ;
Si c.newInstance() est ajouté après l'instruction Load, il y aura une sortie Hi et l'objet de classe sera initialisé ; seulement lorsque la classe est instanciée.
Et si vous le changez en Class.forName("cn.fengd.Dummy"); ou new Dummy() ?
afficheront tous Salut.
Analyse des questions fréquemment posées :
1.由不同的类加载器加载的指定类型还是相同的类型吗?
在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间.我们可以用两个自定义类加载器去加载某自定义类型(注意,不要将自定义类型的字节码放置到系统路径或者扩展路径中,否则会被系统类加载器或扩展类加载器抢先加载),然后用获取到的两个Class实例进行java.lang.Object.equals(…)判断,将会得到不相等的结果。这个大家可以写两个自定义的类加载器去加载相同的自定义类型,然后做个判断;同时,可以测试加载java.*类型,然后再对比测试一下测试结果。
2.在代码中直接调用Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?
Class.forName(String name)默认会使用调用类的类加载器来进行类加载。我们直接来分析一下对应的jdk的代码:
//java.lang.Class.java publicstatic Class<?> forName(String className)throws ClassNotFoundException { return forName0(className, true, ClassLoader.getCallerClassLoader()); } //java.lang.ClassLoader.java // Returns the invoker's class loader, or null if none. static ClassLoader getCallerClassLoader() { // 获取调用类(caller)的类型 Class caller = Reflection.getCallerClass(3); // This can be null if the VM is requesting it if (caller == null) { returnnull; } // 调用java.lang.Class中本地方法获取加载该调用类(caller)的ClassLoader return caller.getClassLoader0(); } //java.lang.Class.java //虚拟机本地实现,获取当前类的类加载器 native ClassLoader getClassLoader0();
3.在编写自定义类加载器时,如果没有设定父加载器,那么父加载器是?
在不指定父类加载器的情况下,默认采用系统类加载器。可能有人觉得不明白,现在我们来看一下JDK对应的代码实现。众所周知,我们编写自定义的类加载器直接或者间接继承自java.lang.ClassLoader抽象类,对应的无参默认构造函数实现如下:
//摘自java.lang.ClassLoader.java protected ClassLoader() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkCreateClassLoader(); } this.parent = getSystemClassLoader(); initialized = true; }
我们再来看一下对应的getSystemClassLoader()方法的实现:
privatestaticsynchronizedvoid initSystemClassLoader() { //... sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); scl = l.getClassLoader(); //... }
我们可以写简单的测试代码来测试一下:
System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());
本机对应输出如下:
sun.misc.Launcher$AppClassLoader@197d257
所以,我们现在可以相信当自定义类加载器没有指定父类加载器的情况下,默认的父类加载器即为系统类加载器。同时,我们可以得出如下结论:
即时用户自定义类加载器不指定父类加载器,那么,同样可以加载如下三个地方的类:
(1)6e5475ece90bdb5cedfc1a16b6632d24/lib下的类
(2)9da487fed862e2e75b98522999bd48b8/lib/ext下或者由系统变量java.ext.dir指定位置中的类
(3)当前工程类路径下或者由系统变量java.class.path指定位置中的类
4.在编写自定义类加载器时,如果将父类加载器强制设置为null,那么会有什么影响?如果自定义的类加载器不能加载指定类,就肯定会加载失败吗?
JVM规范中规定如果用户自定义的类加载器将父类加载器强制设置为null,那么会自动将启动类加载器设置为当前用户自定义类加载器的父类加载器(这个问题前面已经分析过了)。同时,我们可以得出如下结论:
即时用户自定义类加载器不指定父类加载器,那么,同样可以加载到6e5475ece90bdb5cedfc1a16b6632d24/lib下的类,但此时就不能够加载6e5475ece90bdb5cedfc1a16b6632d24/lib/ext目录下的类了。
说明:问题3和问题4的推断结论是基于用户自定义的类加载器本身延续了java.lang.ClassLoader.loadClass(…)默认委派逻辑,如果用户对这一默认委派逻辑进行了改变,以上推断结论就不一定成立了,详见问题5。
5.编写自定义类加载器时,一般有哪些注意点?
(1)一般尽量不要覆写已有的loadClass(…)方法中的委派逻辑
一般在JDK 1.2之前的版本才这样做,而且事实证明,这样做极有可能引起系统默认的类加载器不能正常工作。在JVM规范和JDK文档中(1.2或者以后版本中),都没有建议用户覆写loadClass(…)方法,相比而言,明确提示开发者在开发自定义的类加载器时覆写findClass(…)逻辑。举一个例子来验证该问题:
//用户自定义类加载器WrongClassLoader.Java(覆写loadClass逻辑) publicclassWrongClassLoaderextends ClassLoader { public Class<?> loadClass(String name) throws ClassNotFoundException { returnthis.findClass(name); } protected Class<?> findClass(String name) throws ClassNotFoundException { //假设此处只是到工程以外的特定目录D:/library下去加载类 具体实现代码省略 } }
通过前面的分析我们已经知道,用户自定义类加载器(WrongClassLoader)的默
认的类加载器是系统类加载器,但是现在问题4种的结论就不成立了。大家可以简
单测试一下,现在6e5475ece90bdb5cedfc1a16b6632d24/lib、9da487fed862e2e75b98522999bd48b8/lib/ext和工
程类路径上的类都加载不上了。
问题5测试代码一
publicclass WrongClassLoaderTest { publicstaticvoid main(String[] args) { try { WrongClassLoader loader = new WrongClassLoader(); Class classLoaded = loader.loadClass("beans.Account"); System.out.println(classLoaded.getName()); System.out.println(classLoaded.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }
(说明:D:"classes"beans"Account.class物理存在的)
输出结果:
java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系统找不到指定的路径。) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:106) at WrongClassLoader.findClass(WrongClassLoader.java:40) at WrongClassLoader.loadClass(WrongClassLoader.java:29) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:620) at java.lang.ClassLoader.defineClass(ClassLoader.java:400) at WrongClassLoader.findClass(WrongClassLoader.java:43) at WrongClassLoader.loadClass(WrongClassLoader.java:29) at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27) Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:620) at java.lang.ClassLoader.defineClass(ClassLoader.java:400) at WrongClassLoader.findClass(WrongClassLoader.java:43) at WrongClassLoader.loadClass(WrongClassLoader.java:29) at WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)
这说明,连要加载的类型的超类型java.lang.Object都加载不到了。这里列举的由于覆写loadClass(…)引起的逻辑错误明显是比较简单的,实际引起的逻辑错误可能复杂的多。
问题5测试二
//用户自定义类加载器WrongClassLoader.Java(不覆写loadClass逻辑) publicclassWrongClassLoaderextends ClassLoader { protected Class<?> findClass(String name) throws ClassNotFoundException { //假设此处只是到工程以外的特定目录D:/library下去加载类 具体实现代码省略 } }
将自定义类加载器代码WrongClassLoader.Java做以上修改后,再运行测试代码,输出结果如下:
beans.Account WrongClassLoader@1c78e57
这说明,beans.Account加载成功,且是由自定义类加载器WrongClassLoader加载。
这其中的原因分析,我想这里就不必解释了,大家应该可以分析的出来了。
(2)正确设置父类加载器
通过上面问题4和问题5的分析我们应该已经理解,个人觉得这是自定义用户类加载器时最重要的一点,但常常被忽略或者轻易带过。有了前面JDK代码的分析作为基础,我想现在大家都可以随便举出例子了。
(3)保证findClass(String )方法的逻辑正确性
事先尽量准确理解待定义的类加载器要完成的加载任务,确保最大程度上能够获取到对应的字节码内容。
6.如何在运行时判断系统类加载器能加载哪些路径下的类?
一是可以直接调用ClassLoader.getSystemClassLoader()或者其他方式获取到系统类加载器(系统类加载器和扩展类加载器本身都派生自URLClassLoader),调用URLClassLoader中的getURLs()方法可以获取到;
二是可以直接通过获取系统属性java.class.path 来查看当前类路径上的条目信息 , System.getProperty("java.class.path")
7.如何在运行时判断标准扩展类加载器能加载哪些路径下的类?
方法之一:
try { URL[] extURLs = ((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs(); for (int i = 0; i < extURLs.length; i++) { System.out.println(extURLs[i]); } } catch (Exception e) {//…}
本机对应输出如下:
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/dnsns.jar file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/localedata.jar file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jar file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar
更多深入解析Java中的Class Loader类加载器相关文章请关注PHP中文网!