Small knowledge, big challenge! This paper is participating in theEssentials for programmers”Creative activities
Earlier: Why study class loading? Why study parental delegation?
To study the process of class loading is to know that the parent delegate mechanism is used for class loading. But just knowing parental delegation is not the goal, the goal is to understand why you use parental delegation. What’s the rationale? Know the logical idea of parental delegation and see if that idea can be adapted for our own use. This is the purpose of learning knowledge.
For example, the parent delegate mechanism avoids the reloading of classes and the modification of the core class library. So, when we do the framework design, the bottom of the framework should not be allowed to change, or can not be hacked, so we can learn from the parent delegation mechanism.
Another example: the implementation of parental delegation mechanism uses the chain of responsibility design pattern, we can take a look at the chain of responsibility design pattern, so as to understand the principle of delegation. So what scenarios can we use the chain of responsibility design pattern for? Thinking more is the purpose and essence of learning.
What you learn, and how you apply it to your job, is king.
What is parental delegation
Let’s start with an example: print the bootloader, the extension loader, and the directory loaded by the application class loader
package com.lxl.jvm; import sun.misc.Launcher; import java.net.URL; public class TestJDKClassLoader { public static void main(String[] args) { System.out.println(); System.out.println("bootstrap Loader loads the following files :"); URL[] urls = Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i<urls.length; i++) { System.out.println(urls[i]); } System.out.println(); System.out.println("extClassLoader loads the following file "); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoader loads the following file "); System.out.println(System.getProperty("java.class.path")); }}Copy the code
Let’s take a look:
The bootstrap class loader to load the file is: the Launcher. GetBootstrapClassPath (). The getURLs () of files
The extension class loader loads the file java.ext.dirs, the Java extension class directory
Java.class. path, all classes in the Java Home path
Let’s take a look at the print
The bootstrap Loader loads the following files: File: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/resources. The jar File: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/rt. The jar File: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/sunrsasign jar File: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/jsse jar File: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/jce jar File: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/charsets. The jar File: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib * * * * / JFR in the jar File: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/classes
The extClassLoader loads the following files : / Users/Library/Java/Extensions/Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext: / Library/jar a/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
AppClassLoader loading the following files/Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib * * * * / charsets. The jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/deploy the jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/cldrdata jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/DNSNS jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/jaccess jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/JFXRT jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/localedata jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/nashorn jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/sunec jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/sunjce_provider jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/sunpkcs11 jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/ext/zipfs jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home * * * * / jre/lib/javaws jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/jce jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/JFR jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home * * * * / jre/lib/JFXSWT jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/jsse jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/management – agent. The jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/plugin jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/resources. The jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/jre/lib/rt. The jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/lib * * * * / ant – deployment headaches. Jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/lib * * * * / dt in jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/lib * * * * / deployment headaches – mx. Jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/lib * * * * / jconsole jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/lib * * * * / packager. The jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/lib * * * * / sa – jdi. Jar: / Library/Java/JavaVirtualMachines jdk1.8.0 _181. JDK/Contents/Home/lib * * * * / tools in a jar: /Users/Downloads/workspace/project-all/target/classes: / Users/responsitory/org/springframework/boot/spring – the boot – starter / 2.2.8. RELEASE/spring – the boot – starter – 2.2.8. The jar: / * * * * / Users/responsitory org/springframework/boot/spring – the boot / 2.2.8. RELEASE/spring – the boot – 2.2.8. The jar: / Users/responsitory/org/springframework/spring – the context / 5.2.7. RELEASE/spring – the context – 5.2.7. The jar: / * * * * / Users/responsitory org/springframework/spring aop / 5.2.7. RELEASE/spring aop — 5.2.7. The jar: / Users/responsitory/org/springframework/spring beans / 5.2.7. RELEASE/spring – beans – 5.2.7. The jar: / Users/responsitory/org/springframework/spring – expression / 5.2.7. RELEASE/spring – expression – 5.2.7. The jar: / Users/responsitory/org/springframework/boot/spring – the boot – autoconfigure / 2.2.8. RELEASE/spring – the boot – autoconfigure – 2.2.8. REL EASE.jar: / Users/responsitory/org/springframework/boot/spring – the boot – starter – logging / 2.2.8 RELEASE/spring – the boot – starter – logging – 2.2.8 . RELEASE. The jar: / Users/responsitory/ch/qos/logback/logback – classic / 1.2.3 / logback – classic – 1.2.3. Jar: / Users/responsitory / * * * * ch/qos/logback/logback – core / 1.2.3 / logback – core – 1.2.3. Jar: / Users/responsitory/org/apache/logging/log4j/log4j – to – slf4j / 2.12.1 / log4j – to – slf4j – 2.12.1. Jar: / Users/responsitory/org/apache/logging/log4j/log4j – API / 2.12.1 / log4j – API – 2.12.1. Jar: / Users/responsitory/org/slf4j/jul – to – slf4j / 1.7.30 / jul – to – slf4j – 1.7.30. Jar: / Users/responsitory/Jakarta/annotation/Jakarta. The annotation – API / 1.3.5 / Jakarta. The annotation – API – 1.3.5. Jar: / Users/responsitory/org/springframework/spring – the core / 5.2.7. RELEASE/spring – core – 5.2.7. The jar: / Users/responsitory/org/springframework/spring – the JCL / 5.2.7. RELEASE/spring – the JCL – 5.2.7. The jar: / Users/responsitory/org/yaml/snakeyaml / 1.25 / snakeyaml – 1.25. The jar: / Users/responsitory/org / / 1.7.30 slf4j slf4j/slf4j – API – API – 1.7.30. Jar:
/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar
Through observation, we found that
The boot class loader does load only classes in the /jre/lib directory under Java Home
The extension class loader loads classes in the Java extension directory
However, the application class loader contains classes in the Java home /jre/lib directory, classes in the Java Home extension directory, classes in the responsitory repository, classes in idea, and classes in our classpath target.
The question is, why does the AppClassLoader load the classes that the bootloader and the extension classloader want to load? Isn’t that repetitively loading?
The appClassLoader mainly loads classes in the target directory. Classes in other directories are not actually loaded at all. Why is that? This is because of the parent delegate mechanism.
The diagram above is a diagram of the parent delegate mechanism. This is how class loading works. It is divided into two parts:
- Part of it is finding
- The other part is loading
Taking the custom java.lxl.jvm.Math class as an example, let’s see how this class is loaded by the classloader.
The first step is that the application classloader looks for the java.lxl.jvm.Math class. It looks to see if the java.lxl.jvm.Math class exists in any of the classes it has loaded. Instead, it delegates the load to its parent, the extension class loader.
The extension class loader also searches to see if the loaded class has java.lxl.jvm.Math, and returns if it does, and loads the class if it does not. When loading, it does not load itself, but delegates it to its parent class, which instructs the classloader to load it.
Step 3: Bootstrap the classloader first looks for this class in the loaded class, if yes, return, not to load the class. At this point, as we all know, the Math class is my own definition, and there can’t be any loading failures in the bootstrap classloader, so it’s going to load that class. The lib/jar/ext extension loader scans the lib/jar/ext extension for this class. Of course not, so you delegate the application class loader, ok, there is an application class loader, so you can load it, and then return this class.
Through analysis, we can conclude that the implementation of parental delegation mechanism uses the responsibility chain design pattern.
So, there’s a problem here, which is that the application class loader loads first, and then comes back to the application class loader. I’ve come full circle and come back. Isn’t that a bit superfluous? Twice? Why does it have to be loaded from the application class loader? Isn’t it bad to load directly from the boot class loader? Only loop once….
In fact, for our project, we wrote 95% of the classes ourselves, so the classes we wrote were loaded by the application class loader. In fact, the application class loader only loads twice the first time. In the future, when you use this class again, just ask the application classloader, does this class exist? I already have it, so I just return it.
Source code analysis parent delegation mechanism
Or from this figure, called the sun. The c + + language misc. The Launcher. GetLauncher () to obtain the object of the Launcher, the Launcher class initialization when the constructor to create a ExtClassLoader and AppClassLoader. It then calls the getClassLoader() method of the Launcher object.
public ClassLoader getClassLoader(a) {
return this.loader;
}
Copy the code
GetClassLoader () returns the this.loader object. The Loader object is assigned a value when the Launcher is initialized, and loadClass is the AppClassLoader.
public Launcher(a) {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// The loader value is AppClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9); }... }Copy the code
How does a class loader load a class?
The loader.loadClass(” com.lxl.math “) method is called. Let’s look at the class loader. The classLoader mainly calls classloader.loadclass (” com.lxl.math “) to implement the parent delegate mechanism. From the above analysis, we know that when the Launcher class is initialized, the loadClass is the AppClassLoader, so the parent delegate mechanism starts from the AppClassLoader.
Let’s take a look at the source code, we use the breakpoint to analyze.
2.1 First Search Up
1. Load the target class from AppClassLoader
First, we add a breakpoint to the loadClass(String var1, Boolean var2) method of the AppClassLoader in the Launcher and assign it to our com.lxl.jVM. Math class
Then run the main method of Math, and let’s see how the class is actually loaded
Start the debug debug mode, first entered the Launch AppClassLoader. LoadClass (…). methods
Let’s look at the implementation of this method in detail
All above are doing permission verification, let’s look at the key code.
Launcher.AppClassLoader.loadClass(...)
publicClass<? > loadClass(String var1,boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if(var3 ! = -1) {
SecurityManager var4 = System.getSecurityManager();
if(var4 ! =null) {
var4.checkPackageAccess(var1.substring(0, var3)); }}FindLoadedClass () : findLoadedClass() : findLoadedClass() : findLoadedClass() : findLoadedClass();
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if(var5 ! =null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw newClassNotFoundException(var1); }}else {
// If there is none in the cache, call loadClass to load the class.
return super.loadClass(var1, var2); }}Copy the code
If you look at the comments, we know that this is the first step in the parent delegate mechanism, so now we look in the AppClassLoader, we look for the class that’s already loaded, if we find it, we return it, if we don’t find it, we load the class. There are two steps: one is the source code for findLoaderClass() and the other is super.loadClass(…). The source of
Step 1: Look for the presence of the loaded class
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if(var5 ! =null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw newClassNotFoundException(var1); }}Copy the code
Before calling findLoaderClass(var1), check whether this.ucp. KnownToNotExist (var1) exists in the cache. If so, call this.findLoadedClass(var1). Lookup. FindLoadedClass ultimately calls a local method lookup
private native finalClass<? > findLoadedClass0(String name);Copy the code
Step 2: Load this class for the first time if it has not been loaded before
else {
// If there is none in the cache, call loadClass to load the class.
return super.loadClass(var1, var2);
}
Copy the code
The first load calls super.loadClass(var1,var2), and who is this super? Let’s look at the integration of AppClassLoader
Press Option + Command + U on the MAC to view the integration diagram
We see that AppClassLoader inherits from URLClassLoader, and URLClassLoader inherits from the above four classes, and eventually inherits from a class called ClassLoader, and all classloaders eventually inherit from this ClassLoader class.
Super.loadclass () is called, so let’s see if the URLClassLoader has a loadClass(). Finally, super.loadClass() is a loadClass that inherits the ClassLoader class (….). methods
This is the class that implements the parent delegate mechanism, so let’s see how it implements it.
The current class loader is the AppClassLoader class loader, so the first step is to find out if there’s a class that’s already loaded in the AppClassLoader, and we see that there’s a check here.
Find com.lxl.jvm.Math by calling findLoadedClass(name). So what’s going on in findLoadedClass(Name)? Let’s go inside.
The findLoaderClass(name) method calls its own method, findLoadedClass0, which is native and implemented in c++. We can’t see the implementation details at the bottom. But the general logic is to look for com.lxl.jvm.Math in the loaded Class and return Class information if it does.
If (c == null) {if(c == null);
The parent of the AppClassLoader is null, and the parent of the ExtClassLoader is not null. Parent. LoadClass (name, false);
2. Load the target class from the ExtClassLoader
That is, the loadClass(…) that executes the extension classloader. Let’s look at the ExtClassLoader extension class
There is no loadClass(…) in ExtClassLoader. LoadClass () {loadClass(…) {loadClass(…) {loadClass(…) {loadClass(…) {loadClass(…); So, we continue to debug. We will definitely go to loadClass(…) again. LoadClass is an ExtClassloader loadClass(…). methods
Sure enough, he came to this method again
Class java.lxl.jvm.Math: java.lxl.jvm.Math: java.lxl.jvm.Math: java.lxl.jvm.Math: java.lxl.jvm.Math: java.lxl.jvm.Math The last call is the local method.
As we know, there is no such thing. Then check whether the parent of ExtClassLoader is empty. The parent of ExtClassLoader is the BootStrapClassLoader, and the bootloader is written in c++, so the parent is empty, and the else is executed
3. Search for the BootStrapClassLoader
And the way to do that is to go to the bootloader, BootstrapClassLoad, and see if there’s a class there, so let’s look at the implementation of the bootloader
We find that the final concrete logic is also implemented as a local method. Math, return com.lxl.jvm.Math if it has been loaded, or null if it has not.
C == null. Let’s move on to the code below
And that completes our first upward search. That’s what it looks like graphically
First, the application class loader loads the class, checks whether the application has loaded the class, the result is not, then calls the parent class loader loadClass() method, extends the class loader to find whether the class exists, also does not. So if its parent is empty, and it is, it goes into the boot class loader and looks for that class, and it doesn’t exist in the boot class loader, and returns NULL
2.2 Class loaders delegate down loading
Now, how does the classloader delegate downwards?
1. Start the class loader to load the target class
/lib/jar /lib/jar /lib/jar /lib/jar /lib/jar Finally, null is returned. Then go back to extClassLoader.loadClass (…) .
2. Extend the class loader to load the target class
Next call findClass(name); Find com.lxl.jvm.Math in ExtClassLoader and let’s see how it works. First of all whose method is this? Is ExtClassLoader.
FindClass (name) (ExtClassLoader) (UrlClassLoader)
In findClass(), we see the path to. Replace it with/and add.class after it. What is this doing? Math is replaced by com/ LXL/JVM/math.class, which is the classpath
Then go to the Resource library to see if the path exists. Return null if there is none, and enter the defineClass() method if there is.
Can we find this class in the ExtClassLoader classpath? Obviously not, because this class is our own definition.
They must execute return NULL.
Debug to return NULL; FindClass () for ExtClassLoader. Returns null and goes back to the AppClassLoader loading class
3. The application class loader loads the target class
C is null, and I’m gonna go ahead and execute findClass(name), and I’m gonna go back to URLClassPath’s findClass(name)
FindClass (Name) of AppClassLoader is called. Is the resource still empty? DefineClass (name,res)
What does the defindClass method do? The method is to load the class. Now that the class is found, all you need to do is load it in.
Four steps of class loading
DefindClass () performs the classloading process. The four steps below are: Verify -> Prepare -> parse -> initialize. The part circled in red in the figure below.
Take a look at these four steps:
privateClass<? > defineClass(String name, Resource res)throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('. ');
/ / classes directory for an absolute path, such as: file: / Users/username/workspace/demo/target/classes /
URL url = res.getCodeSourceURL();
if(i ! = -1) {
// Get the package name
String pkgname = name.substring(0, i);
// Check if package already loaded.
Manifest man = res.getManifest();
definePackageInternal(pkgname, man, url);
}
// Now read the class bytes and define the class
java.nio.ByteBuffer bb = res.getByteBuffer();
if(bb ! =null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, b, 0, b.length, cs); }}Copy the code
The core logic is all native methods. What we usually see are some basic validations, such as the preparation phase, the parsing phase, and the initialization phase are all local methods
protected finalClass<? > defineClass(String name,byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
// Pre-defined class information
protectionDomain = preDefineClass(name, protectionDomain);
// Define class source code
String source = defineClassSourceLocation(protectionDomain);
// Initialize the classClass<? > c = defineClass1(name, b, off, len, protectionDomain, source);// Class definition post-processing
postDefineClass(c, protectionDomain);
return c;
}
Copy the code
This code can be understood. You don’t have to go into it.
Above is the source code of the parent delegate mechanism.
So the next time we encounter the com.lxl.jvm.Math class, we already have it in AppClassLoader, and it just returns.
Let’s take a look at the flow chart of the parent delegation mechanism
Third, why should there be a parent delegation mechanism?
There are two reasons: 1. Sandbox security. The java.lang.String.class class is not loaded, which prevents the core API library from being modified. Avoid class reloading. For example, in AppClassLoader, there are Java /jre/lib classes, will it load? No, it lets the top class loader load, and when the top class loader loads, it returns directly, avoiding reloading.Copy the code
Let’s take a look at the following case
Add, I define a String class locally, package name is java.lang.string. Rt. jar (String) {rt.jar (String) {rt.jar (String) {rt.jar (String);
Here’s what happens if we run main. Yes, an error will be reported
The following analysis, why will report an error?
The java.lang.String class is loaded by the AppClassLoader. The java.lang.String class is not loaded by the AppClassLoader. The BootStrapClassLoader is loaded by the BootStrapClassLoader. Java.lang.String is a class in rt.jar, not our own class. After loading the java.lang.String class in rt.jar, I went to the main method, but I did not find….. The result is an exception that the main method cannot be found.
So, if we want to redefine a system-loaded class, such as String.class, while defining it ourselves, is it possible? No, you can’t, because the classes you define are never loaded
This is where parental delegation comes in first: as a sandbox security mechanism, the java.lang.String.class class that you write will not be loaded, which prevents the core API library from being arbitrarily modified
The parent delegate mechanism also has the benefit of avoiding class reloading. For example, in AppClassLoader, there are Java /jre/lib classes, will it load? No, it lets the top class loader load, and when the top class loader loads, it returns directly, avoiding reloading.
Third function: overall delegation mechanism. For example, the Math class defines private User User; Then the user will also be loaded by AppClassLoader. Unless manually specified, use another class loader to load. That is, all other classes called within a class are loaded by the current class loader.