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
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.
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语言实现的
系统类加载器和线程上下文类加载器
在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!