The class loading mechanism is one of the highlights of the Java language, allowing Java classes to be dynamically loaded into the Java Virtual machine.
This time we will leave the jargon and concepts behind and start with examples to explain the Java class loading mechanism from the beginning.
This article involves knowledge points: parent delegate mechanism, BootstrapClassLoader, ExtClassLoader, AppClassLoader, custom network class loader, etc
Article code: GitHub address
More of my articles: Advances in Android Development
What is the Java class loading mechanism?
Java VIRTUAL machines generally use Java classes as follows: Java source code written by the developer (.java files) is first compiled into Java bytecode (.class files), which is then read by the classloader and converted into instances of java.lang.class. With this Class instance in hand, the Java virtual machine can create its real objects using methods like newInstance.
Classloaders are Java Class loaders. Most classloaders inherit from classLoaders and are used to load Class files from different sources.
Where do Class files come from?
As mentioned above, the ClassLoader can load classes from multiple sources.
First, the most common are classes that developers write in their applications, which are in the project directory.
Java. Lang, java.math, java. IO, etc., are located in the $JAVA_HOME/jre/lib/ directory. For example, the java.lang.String class is defined in $JAVA_HOME/jre/lib/rt.jar;
In addition, there are Java core extension classes located in the $JAVA_HOME/jre/lib/ext directory. Developers can also package their own classes into jar files and put them in this directory.
The last option is to dynamically load remote.class files.
Since there are so many different sources, in Java, is there a specific ClassLoader that uniformly loads? Or should multiple ClassLoaders load cooperatively?
Which classloaders are responsible for loading the above classes?
In fact, different loaders are responsible for loading the classes from each of the above four sources.
First, let’s look at the highest level Java core class, the core JAR file in $JAVA_HOME/jre/lib. These classes are the basic classes that Java runs on and are loaded by a loader called BootstrapClassLoader, also known as the root loader/boot loader. Note that BootstrapClassLoader is special in that it does not inherit from ClassLoader, but is implemented internally by the JVM;
Then, you need to load the Java core extension class, which is the JAR file in the $JAVA_HOME/jre/lib/ext directory. These files are loaded by the ExtensionClassLoader, also known as the extended class loader. Of course, jar files developed by users in this directory will also be loaded by ExtClassLoader;
Next are the classes written by the developer in the project. These files are loaded by the AppClassLoader, also known as the System ClassLoader.
Finally, if you want to load it remotely like (local file/network download), you have to define a ClassLoader that copies the findClass() method.
As you can see, Java provides at least four classes for loading classes from different sources.
So how do these classLoaders work together to load a class?
How do these Classloaders cooperatively load the String class?
The String class is one of the most commonly used native Java classes. The question is, how will the JVM load the String class?
Let’s guess.
First, the String class belongs to the Java core class and is located in the $JAVA_HOME/jre/lib directory. As mentioned above, classes in this directory will be loaded by the BootstrapClassLoader. Yes, it is loaded by the BootstrapClassLoader. However, this answer assumes that you already know that String is in the $JAVA_HOME/jre/lib directory.
So, what if you don’t know exactly where the String class is? Or do I want you to load an Unknown class?
Some friends will say that it is easy, just go through all the classes, see where the unknown class is located, and then use the corresponding loader to load.
Yes, that’s a good idea. So how do you iterate?
For example, you can iterate through classes written by the user and load them using AppClassLoader if you find them. Otherwise, go through the Java core class directory and use BootstrapClassLoader to load it. Otherwise, go through the Java extended class library and so on.
This line of thinking is in the right direction, but there is a loophole.
If the developer had forged a java.lang.String class himself, creating a package java.lang in the project with a class named String inside it would have been perfectly fine. So if we use the traversal method above, don’t all the strings used in this project become the fake java.lang.String class? How to solve this problem?
The solution is simple: when looking for a class, first iterate through the highest-level Java core classes, then iterate through the Java core extension classes, and finally iterate through the user-defined classes, stopping as soon as it is found.
In Java, this implementation is also known as parental delegation. In fact, it is very simple, BootstrapClassLoader imagine as core senior leaders, ExtClassLoader imagine as middle-level cadres, AppClassLoader imagine as ordinary civil servants. Every time need to load a class, first to get a system loader AppClassLoader instance (this) getSystemClassLoader ()), and then to the upper layers of the request, by the most superior prior to loading, if the superior feel these classes do not belong to the core classes, Can be delegated to each sub-level responsible person to load themselves.
As shown below:
It’s really according toParents entrust
Method for class loading?
Here are a few examples to verify the above loading method.
Developer custom classes will be created byAppClassLoader
To load?
Create a class file called MusicPlayer in your project with the following contents:
package classloader;
public class MusicPlayer {
public void print() {
System.out.printf("Hi I'm MusicPlayer"); }}Copy the code
Then load MusicPlayer.
private static void loadClass() throws ClassNotFoundException { Class<? > clazz = Class.forName("classloader.MusicPlayer");
ClassLoader classLoader = clazz.getClassLoader();
System.out.printf("ClassLoader is %s", classLoader.getClass().getSimpleName());
}
Copy the code
The printed result is:
ClassLoader is AppClassLoader
Copy the code
You can verify that MusicPlayer is loaded by AppClassLoader.
validationAppClassLoader
Are the parents of ExtClassLoader and BootstrapClassLoader?
The AppClassLoader provides a getParent() method to print and see what’s there.
private static void printParent() throws ClassNotFoundException { Class<? > clazz = Class.forName("classloader.MusicPlayer");
ClassLoader classLoader = clazz.getClassLoader();
System.out.printf("currentClassLoader is %s\n", classLoader.getClass().getSimpleName());
while(classLoader.getParent() ! = null) { classLoader = classLoader.getParent(); System.out.printf("Parent is %s\n", classLoader.getClass().getSimpleName()); }}Copy the code
The printed result is:
currentClassLoader is AppClassLoader
Parent is ExtClassLoader
Copy the code
The ExtClassLoader is the parent of the AppClassLoader, but the BootstrapClassLoader is not. In fact, as mentioned earlier, BootstrapClassLoader is special because it is implemented internally by the JVM, so extClassLoader.getParent () = null.
If I move the MusicPlayer class to$JAVA_HOME/jre/lib/ext
What happens in the directory?
As mentioned above, the ExtClassLoader will load all jar files in the $JAVA_HOME/jre/lib/ext directory. Try putting the MusicPlayer class directly into the $JAVA_HOME/jre/lib/ext directory.
Use the following command to package the musicPlayer.java compilation into a JAR file and place it in the appropriate directory.
javac classloader/MusicPlayer.java
jar cvf MusicPlayer.jar classloader/MusicPlayer.class
mv MusicPlayer.jar $JAVA_HOME/jre/lib/ext/
Copy the code
Jar has been placed in the $JAVA_HOME/jre/lib/ext directory, and the previous MusicPlayer has been removed, this time using AppClassLoader intentionally:
private static void loadClass() throws ClassNotFoundException { ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); // AppClassLoader Class<? > clazz = appClassLoader.loadClass("classloader.MusicPlayer");
ClassLoader classLoader = clazz.getClassLoader();
System.out.printf("ClassLoader is %s", classLoader.getClass().getSimpleName());
}
Copy the code
The printed result is:
ClassLoader is ExtClassLoader
Copy the code
Note Even if it is directly loaded by AppClassLoader, it will still be loaded by ExtClassLoader.
Truly understand the source codeParents entrust
Loading mechanism
Now that we’ve seen some of the features of parental delegation through some examples, let’s look at its implementation code to further our understanding.
Opening the loadClass() method in ClassLoader is the source code to analyze. This method does the following things:
- Check if the target class has ever been loaded and return if it has;
- If not, pass the load request to the parent loader to load it.
- If the parent loader is loaded successfully, return the parent loader.
- If parent is not loaded, it calls the findClass() method and returns the result.
The code is as follows:
protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. Has Class< ever been loaded? > c = findLoadedClass(name);if (c == null) {
long t0 = System.nanoTime();
try {
if(parent ! C = parent. LoadClass (name, parent);false);
} elseC = findBootstrapClassOrNull(name); c = findBootstrapClassOrNull(name) } } catch (ClassNotFoundException e) { // ClassNotFoundException thrownif class not found
// from the non-null parent class loader
}
if(c == null) {// If neither parent is loaded into the target class, call its own findClass() method to search for 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);
}
returnc; } // BootstrapClassLoader will call native methods to load private native Class<? > findBootstrapClass(String name);Copy the code
Read the implementation of the source code I believe can have a more complete understanding.
The cool side of class loaders: custom class loaders
Java’s BootstrapClassLoader, AppClassLoader, and ExtClassLoader were mentioned earlier, which are already provided by Java.
What’s really interesting is the custom class loader, which allows you to dynamically load custom classes at run time from your local disk or network. This allows developers to dynamically fix certain problematic classes, hot-updating code.
Let’s implement a network class loader that dynamically downloads.class files from the network and loads them into a virtual machine for use.
I will write about hot fix/dynamic update later, but I will learn about NetworkClassLoader in Java.
- As a
NetworkClassLoader
It inherits firstClassLoader
; - And then it has to implement
ClassLoader
Within thefindClass()
Methods. Notice, noloadClass()
Method, becauseClassLoader
providesloadClass()
(source code above), it will be based onParents entrust
The mechanism searches for a class and does not call its own until it cannot find itfindClass()
If I just copy itloadClass()
“, that still needs to be realizedParents entrust
Mechanism; - in
findClass()
To do this, download a.class file from the network and convert it into a class object for the virtual machine to use.
The specific implementation code is as follows:
/** * Load class from network */ public class NetworkClassLoader extends ClassLoader { @Override protected Class<? > findClass(String name) throws ClassNotFoundException { byte[] classData = downloadClassData(name); // Download it remotelyif(classData == null) { super.findClass(name); // Not found, throw exception}else {
returndefineClass(name, classData, 0, classData.length); // convert class byte data to Class<? > object }returnnull; } private byte[] downloadClassData(String name) {// Download. Class file String path = from localhost"http://localhost" + File.separatorChar + "java" + File.separatorChar + name.replace('. ', File.separatorChar) + ".class";
try {
URL url = new URL(path);
InputStream ins = url.openStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while((bytesNumRead = ins.read(buffer)) ! = -1) { baos.write(buffer, 0, bytesNumRead); // Store downloaded binary data into ByteArrayOutputStream}return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String getName() {
System.out.printf("Real NetworkClassLoader\n");
return "networkClassLoader"; }}Copy the code
This class is used to download the corresponding.class file from the local Apache server http://localhost/java and convert it to class
return to use.
Use NetworkClassLoader to load the MusicPlayer class on localhost.
- First turn on the
MusicPlayer.class
Placed in/Library/WebServer/Documents/java
MacOS has its own Apache server. This is the default directory of the server. - Execute the following code:
String className = "classloader.NetworkClass"; NetworkClassLoader networkClassLoader = new NetworkClassLoader(); Class<? > clazz = networkClassLoader.loadClass(className);Copy the code
- Normal operation, loading
http://localhost/java/classloader/MusicPlayer.class
Success.
It can be seen that NetworkClassLoader works well. If readers want to use it, they just need to modify the URL concatenation way to use it themselves.
summary
Class loading is a very innovative technology in Java, which makes it possible for future hotfix technology. This paper tries to explain the parent delegate mechanism and custom loader through simple language and appropriate examples, and develops a custom NetworkClassLoader.
Of course, class loading is an interesting technique, and it’s hard to cover everything, like how different class loaders load the same class but get different instances of the same class.
thank you
wingjay