ClassLoader is one of the most mysterious technologies in the Java world, and countless people have struggled to figure out where to go. The article on the net is also one after another, through my own personal identification, the vast majority of the content is misleading others. In this article I will take the reader through ClassLoader thoroughly, and you can read other related articles without further details.
What does a ClassLoader do?
As the name implies, it is used to load classes. It is responsible for converting the bytecode form of a Class into an in-memory Class object. Bytecode can come from a disk file *. Class, jar package *. Class, or a byte stream provided by a remote server. Bytecode is essentially an array of [] bytes with a specific complex internal format.
Many bytecode encryption technologies rely on custom ClassLoaders. The bytecode files are encrypted using tools, and the runtime decrypts the file contents using a customized ClassLoader before loading the decrypted bytecode files.
Each Class object has a classLoader field inside it to identify which classLoader loaded it.
class Class<T> { ... private final ClassLoader classLoader; . }
Copy the code
Lazy loading
The JVM does not load all the required classes at once; it loads on demand, which is lazy loading. As the program runs, it will encounter many new classes that it does not recognize. At this time, it will call ClassLoader to load these classes. Once the load is complete, the Class object is stored in the ClassLoader, so you don’t need to reload it again.
For example, when you call a static method of a Class, the Class must be loaded in the first place, but it does not touch the instance field. The Class of the instance field does not need to be loaded, but it may load the Class of the static field because the static method accesses the static field. The category of the instance field is not loaded until you instantiate the object.
Do their job
JVM running instances can have multiple ClassLoaders, and different classloaders load bytecode files from different places. It can be loaded from different file directories, from different JAR files, or from different service addresses on the network.
There are three important classLoaders built into the JVM, BootstrapClassLoader, ExtensionClassLoader, and AppClassLoader.
The BootstrapClassLoader is responsible for loading the JVM runtime core classes in the JAVA_HOME/lib/rt.jar file, which contains the common built-in library java.xxx.*. For example, java.util.*, java.io.*, java.nio.*, java.lang.*, etc. This ClassLoader is special in that it is implemented in C code and we call it the “root loader.”
Extensionclassloaders are responsible for loading JVM extension classes such as the Swing family, built-in JS engines, XML parsers, and so on. These library names usually start with Javax, Their JAR packages are in JAVA_HOME/lib/ext/*.jar, and there are many jar packages.
AppClassLoader is the direct loader for our users, loading the JAR packages and directories in the path defined in the Classpath environment variable. It usually loads the code we write ourselves and the third-party JAR packages we use.
Jar packages and class files provided by static file servers on the network. The JDK has a built-in URLClassLoader. Users can use the URLClassLoader to load remote libraries by passing the canonical network path to the constructor. URLClassLoader can load not only remote libraries, but also local path libraries, depending on the address form in the constructor. ExtensionClassLoader and AppClassLoader are subclasses of URLClassLoader and load libraries from the local file system.
AppClassLoader can be obtained by the static method getSystemClassLoader() provided by the ClassLoader class. It is what we call the “system ClassLoader”, and it usually loads the class code that we users write. When our main method executes, the loader for the first user class is AppClassLoader.
This transitivity
When a program encounters an unknown class, which ClassLoader does it select to load it? The virtual machine’s strategy is to use the ClassLoader of the caller’s Class object to load the currently unknown Class. What is a caller Class object? When the unknown Class is encountered, the virtual machine must be running a method call (static method or instance method) on which the method hangs, which is the caller Class object. We mentioned earlier that each Class object has a classLoader property that records who is loading the current Class.
Because of the transitivity of the ClassLoader, all lazy-loaded classes are completely responsible for the ClassLoader that originally called the main method, which is called AppClassLoader.
Parents delegate
AppClassLoader is only responsible for loading the system libraries in the Classpath. The AppClassLoader must delegate the loading of the system libraries to the BootstrapClassLoader and ExtensionClassLoader. This is often referred to as “parental delegation”.
When AppClassLoader loads an unknown class name, instead of immediately searching the Classpath, it will first hand the class name to ExtensionClassLoader for loading. If ExtensionClassLoader can load it, Then don’t bother with AppClassLoader. Otherwise it searches the Classpath.
When ExtensionClassLoader loads an unknown class name, it does not immediately search the ext path. It will first pass the class name to the BootstrapClassLoader to load. If BootstrapClassLoader can load it, Then don’t bother with the ExtensionClassLoader. Otherwise, it will search for the JAR packages in the ext path.
The three classloaders form a cascading relationship between father and son. Each ClassLoader is lazy and tries his best to give work to his father, who will do it himself when the father is unable to do it. Each ClassLoader object has a parent property inside that points to its parent loader.
class ClassLoader { ... private final ClassLoader parent; . }
Copy the code
Note that the parent pointer of the ExtensionClassLoader is dotted because its parent value is null, which means that its parent is the “root loader”. If the classLoader property value of a Class object is null, then the Class is also loaded by the root loader.
Class.forName
When using JDBC drivers, we often use the class.forname method to dynamically load driver classes.
Class.forName("com.mysql.cj.jdbc.Driver");
Copy the code
The mysql Driver has a static block of code in the Driver class that executes when the Driver class is loaded. This static code block registers the mysql driver instance with the global JDBC driver manager.
class Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } ...}
Copy the code
The forName method also uses the ClassLoader of the caller’s Class object to load the target Class. However, forName also provides a multi-parameter version that specifies which ClassLoader to use for loading
Class<? > forName(String name, boolean initialize, ClassLoader cl)
Copy the code
This form of forName method can override the limitations of built-in loaders by using custom class loaders that allow us to freely load libraries from any other source. Depending on the transitivity of the ClassLoader, other libraries that the target library passes references to will also be loaded using custom loaders.
Custom loader
There are three important ClassLoader methods loadClass(), findClass(), and defineClass().
The loadClass() method is the entry point for loading the target class. It first looks for the current ClassLoader and its parents to see if the target class has already been loaded. If not, the parents will try to load the target class. FindClass () is called to let the custom loader load the target class itself. The findClass() method of a ClassLoader is overridden by subclasses, and different loaders use different logic to get the bytecode of the target class. After retrieving this bytecode, call defineClass() to convert the bytecode into a Class object. Let me show you the basic process in pseudocode
T = this.findFromLoaded(name); t = this.findFromLoaded(name); If (t == null) {t = this.parent. LoadClass (name)} if(t == null) {t = this.findClass(name); } return t; Class findClass(String name) {throw ClassNotFoundException(); } // Assemble the Class object Class defineClass(byte[] code, String name) {return buildClassFromCode(code, name); }} Class CustomClassLoader extends ClassLoader {class findClass(String name) {byte[] code = findCodeFromSomewhere(name); Return this.defineclass (code, name); }}
Copy the code
Custom classloaders do not easily break the parent delegate rules and do not overwrite loadClass methods. Otherwise, the custom loader may fail to load the built-in core libraries. When using a custom loader, it is important to know who its parent is and pass the parent loader through the constructor of the subclass. If the parent class loader is null, that means the parent is the “root loader.”
// ClassLoader constructor protected ClassLoader(String name, ClassLoader parent);
Copy the code
The parent delegate rule might become a three-parent delegate, four-parent delegate, depending on who the parent loader you’re using, it will recursively delegate all the way to the root loader.
Class.forName vs ClassLoader.loadClass
Both methods can be used to load the target Class, with one small difference: class.forname () gets the Class of the native type, while classloader.loadClass () returns an error.
Class<? > x = Class.forName("[I");System.out.println(x);x = ClassLoader.getSystemClassLoader().loadClass("[I"); System.out.println(x);---------------------class [IException in thread "main" java.lang.ClassNotFoundException: [I...
Copy the code
Diamond rely on
In project management, there is a well-known concept called “diamond dependency”, where software dependencies result in two versions of the same package needing to coexist rather than collide.
Maven resolves diamond dependencies by choosing one of several conflicting versions, and if compatibility is poor, the program will not compile properly. Maven’s form is called “flat” dependency management.
Diamond dependencies can be resolved using ClassLoader. Different versions of software packages use different classloaders to load, and classes with the same name in different classloaders are actually different classes. Let’s try a simple example using URLClassLoader, whose default parent loader is AppClassLoader
$ cat ~/source/jcl/v1/Dep.javapublic class Dep { public void print() { System.out.println("v1"); }}$ cat ~/source/jcl/v2/Dep.javapublic class Dep { public void print() { System.out.println("v1"); }}$ cat ~/source/jcl/Test.javapublic class Test { public static void main(String[] args) throws Exception { String v1dir = "file:///Users/qianwp/source/jcl/v1/"; String v2dir = "file:///Users/qianwp/source/jcl/v2/"; URLClassLoader v1 = new URLClassLoader(new URL[]{new URL(v1dir)}); URLClassLoader v2 = new URLClassLoader(new URL[]{new URL(v2dir)}); Class<? > depv1Class = v1.loadClass("Dep"); Object depv1 = depv1Class.getConstructor().newInstance(); depv1Class.getMethod("print").invoke(depv1); Class<? > depv2Class = v2.loadClass("Dep"); Object depv2 = depv2Class.getConstructor().newInstance(); depv2Class.getMethod("print").invoke(depv2); System.out.println(depv1Class.equals(depv2Class)); }}
Copy the code
Before running, we need to compile the dependent libraries
$ cd ~/source/jcl/v1$ javac Dep.java$ cd ~/source/jcl/v2$ javac Dep.java$ cd ~/source/jcl$ javac Test.java$ java Testv1v2false
Copy the code
In this example, if the two urlClassLoaders point to the same path, the following expression is false, because even the same bytecode loaded by different classloaders is not the same class
depv1Class.equals(depv2Class)
Copy the code
We can also have two different versions of the Dep class implement the same interface, thus avoiding the need to call methods in the Dep class in a reflective manner.
Class<? > depv1Class = v1.loadClass("Dep"); IPrint depv1 = (IPrint)depv1Class.getConstructor().newInstance(); depv1.print()
Copy the code
ClassLoader can resolve dependency conflicts, but it also limits the interfaces of different software packages to be dynamically invoked using reflection or interfaces. Maven does not have this limitation. It relies on the default lazy loading policy of the virtual machine. If a custom ClassLoader is not displayed during the run, then the AppClassLoader is used throughout. So Maven is not a perfect solution to diamond dependency. If you want to know if there are any open source package management tools that can solve diamond dependencies, I recommend you check out SOFA-ARK, ant Financial’s open source lightweight class isolation framework.
Division of Labor and cooperation
To redefine the meaning of a ClassLoader, it acts as a namespace for a class and acts as class isolation. The class name in the same ClassLoader is unique, and different Classloaders can hold classes with the same name. ClassLoader is a container for class names and a sandbox for classes.
There is also cooperation between different Classloaders through the parent attribute and the parent delegate mechanism. Parent has a higher load priority. In addition, parent also expresses a sharing relationship. When multiple child ClassLoaders share the same parent, the classes contained in the parent can be considered to be shared by all the child ClassLoaders. This is why the BootstrapClassLoader is considered the ancestor of all class loaders and the JVM core libraries should naturally be shared.
Thread.contextClassLoader
If you read Thread’s source code a bit, you’ll notice a field in its instance field that’s very special
class Thread { ... private ClassLoader contextClassLoader; public ClassLoader getContextClassLoader() { return contextClassLoader; } public void setContextClassLoader(ClassLoader cl) { this.contextClassLoader = cl; } ...}
Copy the code
ContextClassLoader, contextClassLoader, contextClassLoader, contextClassLoader
First of all, contextClassLoader is the kind of class loader that needs display use, and if you don’t display use it, you’ll never use it anywhere. You can use it in the following way
Thread.currentThread().getContextClassLoader().loadClass(name);
Copy the code
This means that if you load the target class using the forName(String Name) method, it will not automatically use contextClassLoader. Classes that are loaded lazily because of dependencies on code are not automatically loaded using contextClassLoader.
The contextClassLoader for the second thread is inherited from the parent thread, which is the thread that created the current thread. The contextClassLoader for the main thread when the program starts is the AppClassLoader. This means that the contextClassLoaders for all threads are appClassLoaders unless manually set.
So what is this contextClassLoader for? We will use the aforementioned principles of class loader division and cooperation to explain its use.
It can share classes across threads as long as they share the same contextClassLoader. Contextclassloaders are passed automatically between parent and child threads, so sharing will be automated.
If different threads use different ContextClassLoaders, classes used by different threads can be isolated.
If we divide services, different services use different thread pools, thread pools share the same contextClassLoader, and thread pools use different ContextClassloaders, which can play a good role in isolation and protection, avoiding class version conflicts.
If we do not customize the contextClassLoader, then all threads will use the AppClassLoader by default and all classes will be shared.
Thread contextClassLoader usage is rare, so don’t worry if the logic above is obscure.
The structure of the class loader has been modified to some extent in JDK9 with the addition of modules, but the principle of the class loader is similar. It acts as a container for classes, acts as a class isolation, and relies on the parent delegate mechanism to establish cooperation between different class loaders.
Read more articles, long press the picture to identify the QR code to pay attention to the “code hole”