When doing Java development, the basic knowledge that you must be familiar with in the ClassLoader mechanism. This article briefly summarizes the Java ClassLoader mechanism. Because different JVM implementations are different, the content described in this article is limited to Hotspot Jvm.
This article will start with the four aspects of ClassLoader, parent delegation model provided by JDK, how to customize ClassLoader and scenarios that break the parent delegation mechanism in Java.
JDK default ClassLoader
JDK provides the following ClassLoaders by default
Bootstrp loader
The Bootstrp loader is written in C++ language. It is initialized after the Java virtual machine is started. It is mainly responsible for loading the path specified by the %JAVA_HOME%/jre/lib,-Xbootclasspath parameter and the classes in %JAVA_HOME%/jre/classes.
ExtClassLoader
The Bootstrp loader loads the ExtClassLoader, and sets the parent loader of the ExtClassLoader to Bootstrp loader.ExtClassLoader is written in Java, specifically, sun.misc.Launcher$ExtClassLoader. The ExtClassLoader mainly loads %JAVA_HOME%/jre/lib/ext, all classes directories under this path and class libraries in the path specified by the java.ext.dirs system variable.
AppClassLoader
After the Bootstrp loader loads the ExtClassLoader, the AppClassLoader will be loaded, and the parent loader of the AppClassLoader is specified as the ExtClassLoader. AppClassLoader is also written in Java. Its implementation class is sun.misc.Launcher$AppClassLoader. In addition, we know that there is a getSystemClassLoader method in ClassLoader. This method returns the AppclassLoader.AppClassLoader is mainly responsible for loading the class or jar document at the location specified by classpath. It is also the default class loader for Java programs.
Parent delegation model
ClassLoader loading in Java adopts a parent delegate mechanism. When loading classes using a parent delegate mechanism, the following steps are adopted:
Currently, ClassLoader first checks whether this class has been loaded from the class it has already loaded. If it has been loaded, it will directly return the original class.
Each class loader has its own loading cache. When a class is loaded, it will be put into the cache and can be returned directly when it is loaded next time.
When the classLoader's cache is not found, the parent class loader is delegated to load. The parent class loader adopts the same strategy. First, check its own cache, and then delegate the parent class of the parent class to load, all the way to bootstrp ClassLoader.
When all the parent class loaders are not loaded, they are loaded by the current class loader and put them in its own cache so that they can be returned directly the next time there is a loading request.
Speaking of this, you may wonder, why does Java adopt such a delegation mechanism? To understand this problem, we introduce another concept "namespace" about Classloader, which means that to determine a certain class, you need a fully qualified name of the class and load this class ClassLoader to jointly determine it. That is to say, even if the fully qualified names of the two classes are the same, because different ClassLoaders load this class, then it is a different class in the JVM. After understanding the namespace, let’s take a look at the delegate model. After adopting the delegate model, the interactive capabilities of different ClassLoaders are increased. For example, as mentioned above, the class libraries provided by our JDK Binsheng, such as hashmap, linkedlist, etc. After these classes are loaded by the bootstrp class loader, no matter how many class loaders there are in your program, these classes can actually be shared, which avoids confusion caused by different class loaders loading different classes of the same name.
How to customize ClassLoader
In addition to the classloader provided by default mentioned above, Java also allows applications to customize the classloader. If you want to customize the classloader, we need to implement it by inheriting java.lang.ClassLoader. Next, let’s take a look at several important methods we need to pay attention to when customizing the Classloader:
1.loadClass method
loadClass method declare
public Class<?> loadClass(String name) throws ClassNotFoundException
The above is the prototype declaration of the loadClass method. The implementation of the parent delegation mechanism mentioned above is actually implemented in this method. Let's take a look at the code of this method to see how it implements parent delegation.
loadClass method implement
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false);}From the above, we can see that the loadClass method calls the loadcClass(name, false) method, so let's take a look at the implementation of another loadClass method.
Class loadClass(String name, boolean resolve)
protected synchronized Class<?> loadClass(String name, Boolean resolve) throws ClassNotFoundException {// First, check if the class has already been loaded Class c = findLoadedClass(name);//Check whether the class has been loaded if (c == null){try {if (parent != null){c = parent.loadClass(name, false);//If it is not loaded and the parent class loader is specified, the parent loader is delegated to load. } else {c = findBootstrapClass0(name);//If there is no parent class loader, then delegate the bootstrap loader to load}}catch (ClassNotFoundException e) {// If still not found, then invoke findClass in order // to find the class. c = findClass(name);//If the parent class loading is not loaded, it will be loaded through its own findClass. }}if (resolve) {resolveClass(c);}return c;}In the above code, I added comments to clearly see how the parent delegation mechanism of loadClass works. One thing we need to note here is that public Class<?> loadClass(String name) throws ClassNotFoundException is not marked as final, which means that we can override this method, which means that the parent delegation mechanism can be broken. In addition, we noticed that there is a findClass method above. Next, let’s talk about whether this method is a bad one.
2.findClass
We check the source code of java.lang.ClassLoader and we find that the implementation of findClass is as follows:
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name);}We can see that the default implementation of this method is to directly throw exceptions, but in fact, this method is left to our application to override. The specific implementation depends on your implementation logic. You can read from disk or get the byte stream of class files from the network. After obtaining the class binary, you can hand it over to defineClass for further loading. Let's describe defineClass later. OK, through the above analysis, we can draw the following conclusions:
When we write our own ClassLoader, if we want to follow the parent delegation mechanism, we only need to override findClass.
3. defineClass
Let’s first look at the source code of defineClass:
defineClass
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ return defineClass(name, b, off, len, null);}From the above code, we can see that this method is defined as final, which means that this method cannot be override. In fact, this is also the only entry left to us by jvm. Through this unique entry, jvm ensures that the class file must comply with the definition of the class specified in the Java virtual machine specification. This method will finally call the native method to implement the loading of the real class.
Ok, through the above description, let’s think about the following question:
If we wrote a java.lang.String class ourselves, can we replace the class that calls JDK itself?
The answer is no. We cannot achieve it. Why? I see many online explanations that the parent delegation mechanism solves this problem, but it is actually not very accurate. Because the parent delegation mechanism can be broken, you can write a classLoader to load the java.lang.String class you wrote, but you will find that it will not load successfully. Specifically, for classes starting with java.*, the implementation of jvm has ensured that it must be loaded by bootstrp.
Scenarios that do not follow the "parent delegation mechanism"
The above mentioned that the parent delegation mechanism is mainly to realize the interaction problem of classes loaded between different ClassLoaders. The classes that are shared by everyone are handed over to the parent loader to load, but there is indeed a situation in Java where classes loaded by the parent class loader need to use classes loaded by the child loader. Let’s talk about the occurrence of this situation.
There is a SPI (ServiceProviderInterface) standard in Java that uses SPI libraries, such as JDBC, JNDI, etc. We all know that JDBC requires drivers provided by third parties, and the driver's jar package is placed in the classpath of our application itself. The API of jdbc itself is part of the jdk provided by jdk, and it has been loaded by bootstrp. So how do I load the implementation classes provided by third-party manufacturers? JAVA introduces the concept of thread context class loading. The thread class loader will inherit from the parent thread by default. If it is not specified, the default is the system class loader (AppClassLoader). In this way, when a third-party driver is loaded, it can be loaded through the thread's context class loader.
In addition, in order to implement more flexible class loader OSGI and some Javaappservers, it also breaks the parent delegation mechanism.
Summarize
The above is all the content of this article about the code analysis of the usage and usage of the Java Classloader mechanism. I hope it will be helpful to everyone. Interested friends can continue to refer to other related topics on this site. If there are any shortcomings, please leave a message to point it out. Thank you friends for your support for this site!