1, Class loading process
The general process of executing code through Java commands is as follows:
The class loading process of loadClass is divided into several steps:
Load: load the compiled bytecode file into the JVM memory
Verify: verify whether the format of the loaded bytecode file is correct
Preparation: allocate memory to the static variables of the class and assign initial values
Parsing: replace symbolic references with direct references. In this stage, some static methods (such as main method) will be replaced with pointers or handles to the memory where the data is stored [pointers to pointers]. This is the process of static linking [completed during class loading]. The following dynamic links are completed during the creation of objects.
Initialization: assign the specified value to the static variable of the class
Using: using classes
Unload: unload class loader
Note: if other classes are used in the main class during operation, these classes will be loaded step by step instead of one-time. They will be loaded only when they are used
2, The role of Launcher class in class loading
The Launcher class is the startup class of the JVM. Start the JVM hard. The Launcher is created by the boot class loader. It is mainly responsible for creating our extension class loader ExtClassLoader and application class loader AppClassLoader, setting their parent loader, setting AppClassLoader as the system class loader, and so on.
So how does Launcher do it? Let's look at the source code:
Get ExtClassLoader
try { //Method to get extension class loader var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } //The extension class is a static internal class of the Launcher class, which is obtained through the singleton mode realized by double if judgment + synchronized keyword //ExtClassLoader class public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { if (instance == null) { Class var0 = Launcher.ExtClassLoader.class; synchronized(Launcher.ExtClassLoader.class) { if (instance == null) { instance = createExtClassLoader(); } } } return instance; }
Get AppClassLoader
try { //Gets the method of the AppClassLoader class this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { //Get the loading address of the class loader - it can be seen that our AppClassLoader is responsible for loading the classes under the classPath final String var1 = System.getProperty("java.class.path"); final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() { public Launcher.AppClassLoader run() { URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); //Get AppClassLoader return new Launcher.AppClassLoader(var1x, var0); } }); }
So, now you can know where our class loader comes from!!! Next, let's take a look at how the parent delegation mechanism is implemented during class loading.
3, Parental delegation mechanism
The path that the classloader is responsible for loading
Bootstrapclassloader: it is responsible for loading the core class libraries under the lib directory of JRE that support the JVM, such as rt.jar and charsets Jar, etc
Extclassloader: it is responsible for loading the JAR class package in the ext extension directory under the lib directory of JRE that supports the JVM
Application class loader: it is responsible for loading the class packages under the ClassPath path, mainly those classes written by yourself
Custom loader: it is responsible for loading the class package under the user-defined path
Parental delegation mechanism
What is the parental delegation mechanism?
There is no custom class loader by default here
When loading a class, we first find out whether the class has been loaded from the path loaded by the application class loader. If it has been loaded, just return directly; If it is not loaded, check whether the class has been loaded from its parent loader, that is, the extension class loader. If the delegate does not exist, it will be returned upward. If the class is not obtained until the delegate is started, the class will be loaded by the start class loader first. If the class cannot be loaded, it will be loaded by the next level class loader, that is, the extension class loader. If it is not loaded, the next level column class loader, that is, the application class loader, will be responsible for loading until it is loaded into the class. The mechanism of upward delegation and then loading from top to bottom is the parent delegation mechanism.
To sum up: first find the parent class to load, and then find the child class to load if the parent class cannot be loaded
Role of parental delegation mechanism
Speaking of this, we should also understand the role of the parental delegation mechanism. It has two main functions:
1. Ensure sandbox security mechanism: prevent the core class library of JDK from being tampered with.
2. Ensure the uniqueness of class loading: ensure the uniqueness of class loading and do not load the same class repeatedly.
Source code analysis
I will verify whether the parent delegation mechanism is as mentioned above from the perspective of source code:
All classes will inherit the ClassLoader class and call ClassLoader loadClass ("classPath") method to find this class. If it cannot be found, call the findClass() method of URLClassLoader to load this class. The main class loading mechanism is in the loadClass method of ClassLoader
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // Check whether the class has been loaded Class<?> c = findLoadedClass(name); //c == null if (c == null) { long t0 = System.nanoTime(); try { //Judge whether the parent loader is null. At this time, the parent here is ExtClassLoader if (parent != null) { c = parent.loadClass(name, false); } else { //When ExtClassLoader is not found, enter this line of logic. At this time, ExtClassLoader is called Loadclass method, its parent == null. Because BootStrapClassLoader is written in C + + code, it cannot be obtained in Java. If none, the BootStrapClassLoader loads the class first. c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } //If it is not loaded, it returns the upper level method, extclassloader -- > appclassloader if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
The above process is to find out whether the class is loaded from AppClassLoader, then look up ExtClassLoader, and finally look up BootStrapClassLoader + the process from BootStrapClassLoader to ExtClassLoader and then to AppClassLoader. The two processes together are the whole process of parental delegation mechanism.
I don't know if you understand. Take a closer look at the code logic. It's still relatively simple.
Then we can also think, we also want to implement a class loader ourselves, can we? The answer is yes
As mentioned above, all class loaders finally implement the ClassLoader class, and then load the class through the loadClass method. Then it is very simple for us to implement custom class loading. We create a basic ClassLoader class, and then directly use the loadClass() method written in it. We just need to change the findClass() method.
public class MyClassLoaderDemo { public static void main(String[] args) throws Exception { //Initialize the custom loader and automatically assign the class loader of the system to the parent attribute of myClassLoader -- AppClassLoader //Question: how to jump to the assignment method??? //When initializing the custom class loader, the parent class ClassLoader will be initialized first, and the parent loader of the custom class loader will be set as the application class loader AppClassLoader --- that is, the following code /** //Construction method of parent class protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } //Assign the system class loader to the parent attribute of the custom class loader private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; } */ //Define the path for class loading MyClassLoader myClassLoader = new MyClassLoader("/Users/lulupengcheng/Desktop/ClassLoader"); //Load the target class with a custom loader and use the two parent delegation mechanism Class clazz = myClassLoader.loadClass("com.itlaobing.jvm.domain.User"); //Calling methods of a class through reflection Object obj = clazz.newInstance(); Method method = clazz.getMethod("print", null); method.invoke(obj, null); //Loader for output target class System.out.println(clazz.getClassLoader().getClass().getName()); } //Create a class that inherits ClassLoader and override findClass method - the method of loading class static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } //Convert to byte array private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] bytes = loadByte(name); //Method of loading Class -- return Class object return defineClass(name, bytes, 0, bytes.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } } }
In this way, we have implemented a class loader ourselves. It's very simple.
Then let's play a big game. Let's break the parental delegation mechanism!!!
How to break the parental delegation mechanism? As we can see above, the logic of the parent delegation mechanism is mainly implemented in the loadClass method. We only need to do a little in the loadClass method. How to do this depends on the following code:
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); /* 1.This class is obtained by the custom class loader. If it cannot be obtained, the class will be loaded directly through the custom class loader, and the parent loader will not be delegated to obtain or load it * */ if (c == null) { long t0 = System.nanoTime(); // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //Skip the previous logic. If the class is not loaded, directly use the findClass method written by ourselves. c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } }
If it is executed in this way, an error will be reported: Java io. FileNotFoundException: /Users/lulupengcheng/Desktop/ClassLoader/java/lang/Object.class (no such file or directory): object cannot be loaded Class class
Because all classes inherit the Object class, the Object class needs to be loaded first to load subclasses, so an exception will be reported.
How to solve???
1. In this path, set object Copy the class file into it -- no -- the classes of the Java core library cannot be loaded outside
2. Judge whether the parental delegation mechanism needs to be broken according to the package name. If it is a class in the core class library of Java, maintain the parental delegation mechanism. If it is a class under our customized package, break the parental delegation mechanism
Change to
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); /* 1.There is a custom class loader to obtain this class. If it cannot be obtained, it will directly load the class through the custom class loader and will not delegate the parent loader to obtain or load it * */ if (c == null) { long t0 = System.nanoTime(); // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); if (!name.startsWith("com.itlaobing.jvm.domain")){ //If not, load com itlaobing. jvm. The classes under domain maintain the parental delegation mechanism c = this.getParent().loadClass(name); }else { c = findClass(name); } // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } }
Usage scenarios of parental delegation mechanism
Tomcat breaks the parental delegation mechanism.
Why did Tomcat break the parental delegation mechanism?
Since multiple web projects may be deployed under a Tomcat, the technologies used by different web projects must be different. For example, Spring may have different versions. If you follow the parental delegation mechanism, you can only load one version of Spring related classes, which obviously cannot support this scenario. So we need to break the parental delegation mechanism. Not only that, there are many reasons:
-
A web container may need to deploy two applications, and different applications may rely on the same third-party class library
For different versions, it is not required that the same class library is only available on the same server. Therefore, it is necessary to ensure that the class libraries of each application are independent and isolated from each other. -
Deployed in the same web container, the same class library and the same version can be shared. Otherwise, if the server has 10 applications
If so, 10 copies of the same class libraries should be loaded into the virtual machine. -
The web container also has its own dependent class library, which can not be confused with the class library of the application. For security reasons, the class library of the container should be isolated from the class library of the program.
-
The web container needs to support the modification of jsp. We know that the jsp file must be compiled into a class file to run in the virtual machine, but it is common to modify jsp after the program runs. The web container needs to support the modification of jsp without restarting.
Let's take another look at our question: can Tomcat use the default parental delegation class loading mechanism?
The answer is No. Why?
First, if you use the default class loader mechanism, you cannot load different versions of the same class library. No matter what version you are, the default class loader only cares about your fully qualified class name, and there is only one copy.
The second problem is that the default class loader can be implemented because its responsibility is to ensure uniqueness.
The third question is the same as the first.
Let's look at the fourth question. We think how to realize the hot loading of jsp files. jsp files are actually class files
If it is modified, but the class name is still the same, the class loader will directly take the existing and modified jsp in the method area
It will not be reloaded. So what? We can uninstall the classloader of this jsp file directly, so you should think
Here, each jsp file corresponds to a unique class loader. When a jsp file is modified, the jsp class loader will be unloaded directly. Recreate the class loader and reload the jsp file.
So let's see what Tomcat does internally?
As can be seen from the delegation relationship in the figure:
All classes that can be loaded by CommonClassLoader can be used by CatalinaClassLoader and SharedClassLoader, so as to realize the sharing of public class libraries, while the classes that CatalinaClassLoader and SharedClassLoader can load are isolated from each other.
WebAppClassLoader can use the classes loaded by SharedClassLoader, but each WebAppClassLoader instance is isolated from each other.
The loading range of Jasper loader is only the one compiled by the JSP file Class file, which appears to be discarded: when the Web container detects that the JSP file has been modified, it will replace the current JasperLoader instance, and realize the hot loading function of JSP file by establishing a new JSP class loader.
Does the tomcat loading mechanism violate the parental delegation model recommended by java? The answer is: Yes.
Obviously, tomcat is not implemented in this way. In order to achieve isolation, tomcat does not abide by this Convention. Each webappClassLoader loads the class files in its own directory and will not pass them to the parent class loader, breaking the parent delegation mechanism.
Customize Tomcat's class loading mechanism - breaking the parental delegation mechanism
package com.itlaobing.jvm.classLoader; import java.io.FileInputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * @Author: Lu Pengcheng * @Date: 2021/5/3 * @Description: //TODO */ public class MyClassLoaderDemo { public static void main(String[] args) throws Exception { //Initialize the custom loader and assign the class loader of the system to the parent attribute of myClassLoader MyClassLoader myClassLoader = new MyClassLoader("/Users/lulupengcheng/Desktop/ClassLoader"); //Load the target class with a custom loader and use the two parent delegation mechanism Class clazz = myClassLoader.loadClass("com.itlaobing.jvm.domain.User"); //Calling methods of a class through reflection Object obj = clazz.newInstance(); Method method = clazz.getMethod("print", null); method.invoke(obj, null); //Loader for output target class System.out.println("Loader 1---"+ clazz.getClassLoader()); MyClassLoader myClassLoader1 = new MyClassLoader("/Users/lulupengcheng/Desktop/ClassLoader1"); //Load the target class with a custom loader and use the two parent delegation mechanism Class clazz1 = myClassLoader1.loadClass("com.itlaobing.jvm.domain.User"); //Calling methods of a class through reflection Object obj1 = clazz.newInstance(); Method method1 = clazz1.getMethod("print", null); method.invoke(obj1, null); //Loader for output target class System.out.println("Loader 2---"+ clazz1.getClassLoader()); } /*---------------------------------The following code is the same as above to break the parental delegation mechanism---------------------------------*/
result
Custom class loader Loader 1---com.itlaobing.jvm.classLoader.MyClassLoaderDemo$MyClassLoader@610455d6 Custom class loader Loader 2---com.itlaobing.jvm.classLoader.MyClassLoaderDemo$MyClassLoader@5e2de80c