This article mainly introduces the relevant information of class loader in Java in detail, which has certain reference value. Interested friends can refer to it
From the dynamic nature of java To the class loading mechanism
Java is a dynamic language. So how to understand this "dynamic"? In other words, what characteristics does a language have to be called a dynamic language? For java, this is how I understand it.
JVM (java virtual machine) does not execute local machine code instructions, but executes a type of instruction called bytecode (existing in class files). This requires the virtual machine to load the relevant class files into memory before actually executing the bytecode. The virtual machine does not load all required class files at once, because it does not know which class files will be used in the future when it is executed. Every time a class is used, the class file related to this class will be "dynamically" loaded at runtime. This is the fundamental reason why Java is called a dynamic language. In addition to dynamically loading classes, classes are also dynamically initialized and dynamically linked. Dynamic initialization and dynamic linking are introduced in other articles. This article only cares about class loading.
The class loader (ClassLoader) that this article will introduce is responsible for loading classes in the JVM. Therefore, the class loader is an indispensable and important component of the JVM.
Class loaders in java and working principles of class loaders
There are three class loaders in java (referring to javase). Each class loader has specified their corresponding directory when it is created, which means that where each class loader goes to load classes is determined. I think there should be methods such as getTargetPath() in the ClassLoader class, and get I looked for their corresponding paths in the jdk documentation and found that they were not available. The following are these three class loaders and their corresponding paths:
* AppClassLoader -- Load the classes in the path specified by classpath
* ExtClassLoader -- Load the jre/lib/ext directory or java.ext Classes in the directory defined by the .dirs system property
*BootStrap-- Loading classes in JRE/lib/rt.jar
So how does the class loader work? You can refer to the source code of the ClassLoader class in jdk. The implementation of this class uses the template method pattern. First, the loadClass method loads the class. The loadClass method then calls the findClass method. This method reads and returns the data of the class file. After the findClass method returns, the loadClass method continues to call the defineClass method. The returned data is processed into type information that can be recognized when the virtual machine is running. Therefore, if we develop our own class loader, we only need to inherit the ClassLoader class in jdk and override the findClass method. The parent class will do the rest. Some other Java platforms have implemented their own specific class loaders according to their own needs, such as the tomcat server in the Javaee platform, and the Dalvik virtual machine in the Android platform also defines its own class loader.
There are two ways for virtual machines to load classes. One way is the ClassLoader.loadClass() method mentioned above, and the other is to use the reflection API, Class.forName() method. In fact, Class.forName( ) method is also used inside the ClassLoader. The implementation of the forName methodin the Class class is as follows:
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;
Three characteristics of the class loader
The class loader has three characteristics, namely delegation, visibility and singleness. The introduction of these three characteristics in other articles is as follows:
* The delegation mechanism refers to handing over the request to load a class to the parent Class loader, if the parent class loader cannot find or load the class, then load it again.
* The principle of visibility is that the child class loader can see all classes loaded by the parent class loader, but the parent class loader cannot see the classes loaded by the child class loader.
* The principle of unity means that a class is only loaded once. This is because the delegation mechanism ensures that the child class loader will not load the class loaded by the parent class loader again.
Among them, the delegation mechanism is the foundation. This mechanism is also called the parent delegation model of the class loader in other materials. In fact, it means the same thing. Additivity and unity are dependent on the delegation mechanism.
The following code tests the delegation mechanism of the class loader:
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语言实现的
As can be seen from the printing results, the loader that loads the class we wrote ourselves is AppClassLoader, the parent loader of AppClassLoader is ExtClassLoader, and the parent loader of ExtClassLoader returns null, which means that its additional loader is BootStrap. This loader is closely related to the virtual machine. When the virtual machine starts, , the classes in jdk will be loaded. It is implemented in C and has no corresponding java object, so null is returned. But logically, BootStrap is still the parent loader of ExtClassLoader. That is to say, whenever ExtClassLoader loads a class, it will always be delegated to BootStrap for loading.
系统类加载器和线程上下文类加载器
在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语言实现的 } } 类加载器的工作原理>
The above is the detailed content of Detailed explanation of class loader code in Java. For more information, please follow other related articles on the PHP Chinese website!