Home >Java >javaTutorial >An in-depth analysis of the Class Loader class loader in Java
The process of class loading
The main job of the class loader is to load class files into the JVM. As shown in the figure below, the process is divided into three steps:
1. Loading: locate the class file to be loaded, and load its byte stream into the JVM;
2. Link: give the class file to be loaded A class allocates the most basic memory structure to hold its information, such as properties, methods, and referenced classes. At this stage, the class is still unavailable;
(1) Verification: Verify the loaded byte stream, such as format and security;
(2) Memory allocation: For this class Prepare memory space to represent its properties, methods and referenced classes;
(3) Analysis: Load other classes referenced by this class, such as parent classes, implemented interfaces, etc.
3. Initialization: Assign values to class variables.
Levels of class loaders
Above the dotted line in the figure below are several important class loaders provided by the JDK. The detailed description is as follows:
( 1) Bootstrap Class Loader: When starting the class containing the main function, load the jar package in the JAVA_HOME/lib directory or the directory specified by -Xbootclasspath;
(2) Extention Class Loader: Load the JAVA_HOME/lib/ext directory or The jar package in the directory specified by -Djava.ext.dirs.
(3) System Class Loader: Load classes or jar packages in the directory specified by classpath or -Djava.class.path.
What you need to know is:
1. Except for Bootstrap Class Loader, other class loaders are java.lang.ClassLoader Subclass of class;
2.Bootstrap Class Loader is not implemented in Java. If you do not use a personalized class loader, then java.lang.String.class.getClassLoader() will be null, and the parent of Extension Class Loader is loaded. The device is also null;
3. Several ways to obtain the class loader:
(1) Obtain the Bootstrap Class Loader: If you try to obtain the Bootstrap Class Loader, you must get null. You can verify it in the following way: use the getClassLoader method of the class object in the rt.jar package, such as java.lang.String.class.getClassLoader() to get or obtain the Extention Class Loader, and then call the getParent method to obtain it;
( 2) Obtain Extention Class Loader: Use the getClassLoader method of the class object in the jar package in the JAVA_HOME/lib/ext directory or obtain the System Class Loader first, and then obtain it through its getParent method;
(3) Obtain the System Class Loader: Call the getClassLoader method of the class object containing the main function or call Thread.currentThread().getContextClassLoader() or call ClassLoader.getSystemClassLoader() within the main function;
(4) Obtain the User-Defined Class Loader: Call the class object getClassLoader method or call Thread.currentThread().getContextClassLoader();
1. Proxy principle
2. Visibility principle
3. Uniqueness principle
4. Proxy principle
The proxy principle means that a class loader will request its parent to load a class. The parent loader will also request its parent loader agent to load, as shown in the figure below.
1. When loading a class, the class loader first checks whether it has loaded the class. If it has been loaded, return; otherwise, ask the parent loader to proxy;
2. The parent loader repeats the operation of 1. Go to Bootstrap Class Loader;
3. If Bootstrap Class Loader does not load the class, it will try to load it, and return if the loading is successful; if it fails, a ClassNotFoundException will be thrown, and the child loader will load it;
4. Child The class loader catches the exception and tries to load it. If it succeeds, it returns. If it fails, it throws ClassNotFoundException until the subclass loader that initiates the loading.
This understanding is correct for Bootstrap Class Loader, Extention Class Loader, System Class Loader, but some personalized loaders are not. For example, some class loaders implemented by IBM Web Sphere Portal Server are Parent Last, the child loader tries to load first. If the loading fails, the parent loader will be called. The reason for this is: if you expect a certain version of log4j to be used by all applications, put it in the WAS_HOME library, WAS It is loaded on startup. If an application wants to use another version of log4j, this cannot be achieved if Parent First is used, because the classes in log4j have already been loaded in the parent loader. But if you use Parent Last, the class loader responsible for loading the application will load another version of log4j first.
Visibility Principle
The visibility of each class to the class loader is different, as shown in the figure below.
Expand knowledge, OSGi takes advantage of this feature. Each bundle is loaded by a separate class loader, so each class loader can load a version of a certain class, so the entire system can use one Multiple versions of a class.
<br/>
Uniqueness principle
Each class can be loaded at most once in a loader.
Extended knowledge 1: To be precise, the singleton referred to by the Singleton pattern refers to only one copy of an object of a certain class in a group of class loaders.
Extended knowledge 2: A class can be loaded by multiple class loaders. Each class object is in its own namespace. When comparing class objects or performing type conversion on instances, their respective names will be compared at the same time. Space, for example:
The Klass class is loaded by ClassLoaderA, assuming the class object is KlassA; it is also loaded by ClassLoaderB, assuming the class object is KlassB, then KlassA is not equal to KlassB. At the same time, ClassCastException will be thrown when an instance of ClassA is cast to KlassB.
Why personalized class loader
Personalized class loader adds a lot of flexibility to the Java language. Its main uses are:
1. Classes can be loaded from multiple places, such as the network on the database, or even compile the source file immediately to obtain the class file;
2. The personalized class loader can load a certain version of the class file in principle at runtime;
3. The personalized class The loader can dynamically unload some classes;
4. After personalization, the class loader can decrypt and decompress the class before loading the class.
Implicit and explicit loading of classes
Implicit loading: When a class is referenced, inherited or instantiated, it will be loaded implicitly. If the loading fails, it will be thrown NoClassDefFoundError.
Explicit loading: Use the following method. If the loading fails, a ClassNotFoundException will be thrown.
cl.loadClass(), cl is an instance of the class loader;
Class.forName(), uses the class loader of the current class to load.
Execution of static blocks of classes
Suppose there is the following class:
package cn.fengd; public class Dummy { static { System.out.println("Hi"); } }
Create another test class:
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(); } } }
What is the effect after running?
Hi will not be output. It can be seen that the Class object is not initialized after using loadClass;
If c.newInstance(); is added after the Load statement, there will be Hi output, and the class object is initialized only when the class is instantiated.
If you change the loading statement Class c = Class.forName("cn.fengd.Dummy", false, ClassLoader.getSystemClassLoader());
Hi will not be output. Because the parameter false means that there is no need to initialize the object of this class;
If you add c.newInstance(); after the Load statement, there will be Hi output, and the class object will be initialized only when the class is instantiated.
What if you change it to Class.forName("cn.fengd.Dummy"); or new Dummy()?
will output Hi.
Analysis of common problems:
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中文网!