Maison  >  Article  >  Java  >  Explication détaillée du code du chargeur de classe en Java

Explication détaillée du code du chargeur de classe en Java

黄舟
黄舟original
2017-06-18 09:44:271526parcourir

Cet article présente principalement en détail les informations pertinentes sur le chargeur de classe en Java, qui ont une certaine valeur de référence. Les amis intéressés peuvent se référer à

De la nature dynamique de Java Au mécanisme de chargement de classe <.>

Java est un langage dynamique. Alors comment comprendre cette « dynamique » ? En d’autres termes, quelles sont les caractéristiques d’une langue pour être qualifiée de langue dynamique ? Pour Java, c'est ainsi que je le comprends.

JVM (machine virtuelle Java) n'exécute pas d'instructions de code machine local, mais exécute un type d'instruction appelé bytecode (existant dans les fichiers de classe). Cela nécessite que la machine virtuelle charge les fichiers de classe pertinents en mémoire avant d'exécuter réellement le bytecode. La machine virtuelle ne charge pas tous les fichiers de classe requis en même temps, car elle ne sait pas quels fichiers de classe seront utilisés à l'avenir lors de son exécution. Chaque fois qu'une classe est utilisée, le fichier de classe lié à cette classe sera chargé "dynamiquement" au moment de l'exécution. C’est la raison fondamentale pour laquelle Java est appelé langage dynamique. En plus du chargement dynamique des classes, les classes sont également initialisées et liées dynamiquement. L'initialisation dynamique et la liaison dynamique sont présentées dans d'autres articles. Cet article ne s'intéresse qu'au chargement des classes.

Le chargeur de classe (ClassLoader) qui sera présenté dans cet article est responsable du chargement des classes dans la JVM. Par conséquent, le chargeur de classe est un composant indispensable et important de la JVM.

Les chargeurs de classes en Java et le principe de fonctionnement des chargeurs de classes

Il existe trois chargeurs de classes en Java (faisant référence à Javase). Chaque chargeur de classe a spécifié son répertoire correspondant lors de sa création, ce qui signifie que l'endroit où chaque chargeur de classe va charger les classes est déterminé. Je pense qu'il devrait y avoir des méthodes telles que getTargetPath() dans la classe ClassLoader. J'ai recherché leurs chemins correspondants. dans la documentation jdk et j'ai constaté qu'ils n'étaient pas disponibles. Voici ces trois chargeurs de classes et leurs chemins correspondants :

* AppClassLoader -- Charge les classes dans le chemin spécifié par classpath

* ExtClassLoader -- Charge le répertoire jre/lib/ext ou java.ext Classes dans le répertoire défini par la propriété système .dirs
*
BootStrap -- Chargement des classes dans JRE/lib/rt.jar

Alors, comment fonctionne le chargeur de classes ? Vous pouvez vous référer au code source de la classe ClassLoader dans jdk. L'implémentation de cette classe utilise le modèle de méthode modèle.Tout d'abord, la méthode loadClass charge la classe.La méthode loadClass appelle la méthode findClass.Cette méthode lit et renvoie les données du fichier de classe,la méthode loadClass continue. pour appeler la méthode DefinClass. Les données renvoyées sont traitées en informations de type qui peuvent être reconnues lorsque la machine virtuelle est en cours d'exécution. Par conséquent, si nous développons notre propre chargeur de classe, il nous suffit d'hériter de la classe ClassLoader dans jdk et de remplacer la méthode findClass. La classe parent fera le reste. Certaines autres plates-formes Java ont implémenté leurs propres chargeurs de classes spécifiques en fonction de leurs propres besoins, comme le serveur Tomcat dans la plate-forme Javaee, et la machine virtuelle Dalvik dans la plate-forme Android définit également son propre chargeur de classes.

Il existe deux façons pour une machine virtuelle de charger des classes. L'une est la méthode ClassLoader.loadClass() mentionnée ci-dessus, et l'autre consiste à utiliser l'API de réflexion, la méthode Class.forName(). , la méthode Class.forName() est également utilisée dans le ClassLoader. L'implémentation de la méthode for

Name dans la classe Class est la suivante :


public static Class<?> forName(String name, boolean initialize,
  ClassLoader loader)
  throws ClassNotFoundException
 {
 if (loader == null) {
  SecurityManager sm = System.getSecurityManager();
  if (sm != null) {
 ClassLoader ccl = ClassLoader.getCallerClassLoader();
 if (ccl != null) {
  sm.checkPermission(
 SecurityConstants.GET_CLASSLOADER_PERMISSION);
 }
  }
 }
 return forName0(name, initialize, loader);
 }

 /** Called after security checks have been made. */
 private static native Class forName0(String name, boolean initialize,
   ClassLoader loader)
 throws ClassNotFoundException;

Trois fonctionnalités du chargeur de classe

Le chargeur de classe a trois caractéristiques, à savoir la délégation, la visibilité et l'unicité. L'introduction de ces trois caractéristiques dans d'autres articles est la suivante :

* Le mécanisme de délégation fait référence à la remise de la demande. pour charger une classe Vers le chargeur de classe parent, si le chargeur de classe parent ne peut pas trouver ou charger la classe, chargez-la à nouveau.

* Le principe de visibilité est que le chargeur de classe enfant peut voir toutes les classes chargées par le chargeur de classe parent, mais le chargeur de classe parent ne peut pas voir les classes chargées par le chargeur de classe enfant.
* Le principe d'unité signifie qu'une classe n'est chargée qu'une seule fois. En effet, le mécanisme de délégation garantit que le chargeur de classe enfant ne chargera pas à nouveau la classe chargée par le chargeur de classe parent.

Parmi eux, le mécanisme de délégation est la base. Ce mécanisme est également appelé modèle de délégation parent du chargeur de classe dans d'autres matériaux, ce qui signifie en fait la même chose. L'additivité et l'unité dépendent du mécanisme de délégation.


Le code suivant teste le mécanisme de délégation du chargeur de classe :


 ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
 System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 
 
 ClassLoader extClassLoader = appClassLoader.getParent();
 System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@addbf1
 //AppClassLoader的父加载器是ExtClassLoader
 
 System.out.println(extClassLoader.getParent()); //null
 //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的
Comme le montrent les résultats de l'impression, le chargement de la classe que nous avons écrite nous-mêmes. Le chargeur est AppClassLoader et le chargeur parent d'AppClassLoader est ExtClassLoader. Le chargeur parent d'ExtClassLoader renvoie null, ce qui signifie que son chargeur supplémentaire est BootStrap. machine Au démarrage, les classes de jdk seront chargées. Il est implémenté en C et n'a pas d'objet Java correspondant, donc null est renvoyé. Mais logiquement, BootStrap est toujours le chargeur parent d'ExtClassLoader. C'est-à-dire que chaque fois qu'ExtClassLoader charge une classe, elle sera toujours déléguée à BootStrap pour le chargement.

系统类加载器和线程上下文类加载器 

在java中,还存在两个概念,分别是系统类加载器和线程上下文类加载器。 

其实系统类加载器就是AppClassLoader应用程序类加载器,它两个值得是同一个加载器,以下代码可以验证: 


 ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
 System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 
 ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
 System.out.println(sysClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的

 这两个类加载器对应的输出,不仅类名相同,连对象的哈希值都是一样的,这充分说明系统类加载器和应用程序类加载器不仅是同一个类,更是同一个类的同一个对象。

每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程的上下文类加载器, 也就是AppClassLoader。 


 new Thread(new Runnable() {
 
 @Override
 public void run() {
 ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
 System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f
 }
 }).start();

这个子线程在执行时打印的信息为sun.misc.Launcher$AppClassLoader@19821f,可以看到和主线程中的AppClassLoader是同一个对象(哈希值相同)。 

也可以为线程设置特定的类加载器,这样的话,线程在执行时就会使用这个特定的类加载器来加载使用到的类。如下代码: 


 Thread th = new Thread(new Runnable() {
 
 @Override
 public void run() {
 ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
 System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
 }
 });
 
 th.setContextClassLoader(new ClassLoader() {});
 
 th.start();

在线程运行之前,为它设置了一个匿名内部类的类加载器对象,线程运行时,输出的信息为:jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74,也就是我们设置的那个类加载器对象。 

类加载器的可见性 

下面验证类加载器的可见性,也就是 子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。 

以下代码使用父加载器ExtClassLoader加载子加载器AppClassLoader路径下的类,由输出可知,是不可能实现的。 


 try {
 Class.forName("jg.zhang.java.testConcurrent.Person", true, 
  ClassLoaderTest.class.getClassLoader().getParent());
 System.out.println("1 -- 类被加载");
 } catch (ClassNotFoundException e) {
 //e.printStackTrace();
 System.out.println("1 -- 未找到类");
 }

输出为 :1 -- 未找到类 。说明抛出了ClassNotFoundException异常。原因是让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下,所以抛出ClassNotFoundException。所以父加载器不能加载应该被子加载器加载的类。也就是说这个类在父加载器中不可见。这种机制依赖于委派机制。 

下面代码使用子加载器AppClassLoader 加载父加载器BootStrap中的类,这是可以实现的。


 try {
 Class.forName("java.lang.String", true, 
  ClassLoaderTest.class.getClassLoader());
 System.out.println("2 -- 类被加载");
 } catch (ClassNotFoundException e) {
 //e.printStackTrace();
 System.out.println("2 -- 未找到类");
 }

 输出为:2 -- 类被加载。说明成功加载了String类。是因为在指定由AppClassLoader加载String类时,由AppClassLoader一直委派到BootStrap加载。虽然是由子加载器的父加载器加载的,但是也可以说,父加载器加载的类对于子加载器来说是可见的。这同样依赖于委派机制。其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了,这时再次加载,虚拟机发现已经加载,不会再重复加载。这同时也证明了类加载器的单一性。 

测试代码 

到此为止,类加载器的知识就全部讲完了。以下是整个测试代码: 


package jg.zhang.java.testclassloader;


/**
 * 参考ImportNew上的一篇文章<<类加载器的工作原理>>,
 * 文章地址:http://www.importnew.com/6581.html
 * 
 * Java类加载器基于三个机制:委托、可见性和单一性。
 * 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。
 * 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
 * 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
 * 
 * 三种类加载器: 每个类加载器在创建的时候已经指定他们对应的目录, 也就是说每个类加载器去哪里加载类是确定的
 * 我认为在ClassLoader类中应该会有getTargetPath()之类的方法, 得到他们对应的路径,找了找jdk的文档,发现是没有的.
 * AppClassLoader -- 加载classpath指定的路径中的类
 * ExtClassLoader -- 加载jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下的类
 * BootStrap  -- 加载JRE/lib/rt.jar中的类
 * 
 * 
 * 
 * @author zhangjg
 *
 */
public class ClassLoaderTest {

 
 public static void main(String[] args) {
 test1();
 test2();
 test3();
 }

 /**
 * 验证线程上下文类加载器
 */
 private static void test3() {
 /**
 * 1 每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程
 * 的上下文类加载器, 也就是AppClassLoader
 */
 new Thread(new Runnable() {
 
 @Override
 public void run() {
 ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
 System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f
 }
 }).start();
 
 /**
 * 2 也可以给创建的线程设定特定的上下文类加载器
 */
 Thread th = new Thread(new Runnable() {
 
 @Override
 public void run() {
 ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
 System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
 }
 });
 
 th.setContextClassLoader(new ClassLoader() {});
 
 th.start();
 }

 /**
 * 测试可见性,可见性依赖于委托机制
 */
 private static void test2() {
 
 /**
 * 1 让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类
 * 因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下
 * 所以抛出ClassNotFoundException
 * 
 * 所以父加载器不能加载应该被子加载器加载的类,这个类在父加载器中不可见
 * 这种机制依赖于委派机制
 */
 
 try {
 Class.forName("jg.zhang.java.testConcurrent.Person", true, 
  ClassLoaderTest.class.getClassLoader().getParent());
 System.out.println("1 -- 类被加载");
 } catch (ClassNotFoundException e) {
 //e.printStackTrace();
 System.out.println("1 -- 未找到类");
 }
 
 
 /**
 * 2 让AppClassLoader加载java.lang.String类
 * 没有抛出异常,说明类被正常加载了
 * 虽然是由AppClassLoader一直委派到BootStrap而加载的
 * 所以可以说,父加载器加载的类对于子加载器来说是可见的,这同样依赖于委派机制
 * 
 * 其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了
 * 这时再次加载,虚拟机发现已经加载,不会再重复加载
 */
 try {
 Class.forName("java.lang.String", true, 
  ClassLoaderTest.class.getClassLoader());
 System.out.println("2 -- 类被加载");
 } catch (ClassNotFoundException e) {
 //e.printStackTrace();
 System.out.println("2 -- 未找到类");
 }
 
 }

 /**
 * 验证三种类加载器的父子关系
 */
 private static void test1() {
 ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
 System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 
 ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
 System.out.println(sysClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的
 
 ClassLoader extClassLoader = appClassLoader.getParent();
 System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@addbf1
 //AppClassLoader的父加载器是ExtClassLoader
 
 System.out.println(extClassLoader.getParent()); //null
 //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的

 }

}

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn