A: this
As you can see from the JVM structure diagram, the class loader is used to load Java class files into the Java Virtual machine.
HotSpot JVM Architecture from Java Garbage Collection Basics
Only when a class is loaded into the vm memory can it be used.
In Java, the class loading process is roughly divided into the following steps:
- Gets an array of class file bytes by the full class name. It can come from local files, JAR packages, networks, and so on.
- Store class description and static properties in the method area/meta-space.
- Generate a corresponding java.lang.Class object in the JVM heap.
Understanding Java’s classloading mechanism is a great help in understanding the JVM.
Two: Java’s default class loader
Java provides three class loaders by default:
- Bootstrap ClassLoader
- Extension ClassLoader
- App ClassLoader
Bootstrap ClassLoader loads basic Java classes, such as rt.jar, resources. Jar, and charsets. Jar in %JRE_HOME%/lib/.
The Extension ClassLoader is responsible for loading Java Extension classes, mainly the JAR in the %JRE_HOME%/lib/ext directory.
The App ClassLoader is responsible for loading all classes in the current application’s ClassPath.
You can view the classes loaded by the three classLoaders in the following ways.
public class ClassPath { public static void main(String[] args) { System.out.println("Bootstrap ClassLoader path: "); System.out.println(System.getProperty("sun.boot.class.path")); System.out.println("----------------------------"); System.out.println("Extension ClassLoader path: "); System.out.println(System.getProperty("java.ext.dirs")); System.out.println("----------------------------"); System.out.println("App ClassLoader path: "); System.out.println(System.getProperty("java.class.path")); System.out.println("----------------------------"); }}Copy the code
Specific reasons are explained in the source code analysis section.
The Bootstrap ClassLoader is JVM level and written in C++.
Extension ClassLoader and App ClassLoader are Java classes.
The JVM starts the Bootstrap ClassLoader and initializes sun.misc.Launcher.
Next, the Launcher initializes the Extension ClassLoader and App ClassLoader.
Three: source code analysis
The Sun.misc.Launcher class is the entry point to a Java program.
Its constructor is as follows:
public Launcher() { Launcher.ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); ... }Copy the code
There are two important lines of code:
Launcher.ExtClassLoader.getExtClassLoader();
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
Copy the code
The first line initializes the ExtClassLoader without specifying its parent.
Some articles indicate that the parent of ExtClassLoader is Bootstrap ClassLoader, which is not entirely accurate.
The second line initializes AppClassLoader, specifying ExtClassLoader as its parent loader. AppClassLoader is used as the system class loader.
AppClassLoader will be the default parent loader for custom ClassLoaders.
The logic can be viewed in the following order:
- The getClassLoader() method of the Launcher class.
- InitSystemClassLoader () method of the ClassLoader class.
- The getSystemClassLoader() method of the ClassLoader class.
- ClassLoader The ClassLoader() method of the class.
The getSystemClassLoader() method is commented as:
/**
* Returns the system class loader for delegation. This is the default
* delegation parent for new <tt>ClassLoader</tt> instances, and is
* typically the class loader used to start the application.
**/
Both ExtClassLoader and AppClassLoader inherit the URLClassLoader class.
URLClassLoader supports loading classes from file directories and JAR packages.
Both ExtClassLoader and AppClassLoader call the parent class constructor.
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory)
Copy the code
The URLClassLoader class has an attribute ucP that represents the path that the ClassLoader is responsible for searching.
The biggest difference between ExtClassLoader and AppClassLoader is that they are responsible for different paths.
/* The search path for classes and resources */
private final URLClassPath ucp;
Copy the code
View the source code available:
The ExtClassLoader is responsible for the search path:
String var0 = System.getProperty("java.ext.dirs");
Copy the code
The AppClassLoader searches for the following paths:
String var1 = System.getProperty("java.class.path");
Copy the code
So, in the previous section, you could use these two methods to get directories loaded by different Classloaders.
In addition, Bootstrap ClassLoader is responsible for searching the following path:
String bootClassPath = System.getProperty("sun.boot.class.path");
Copy the code
This source code
ClassLoader is an abstract class. The main methods are as follows:
-
DefineClass (String name, byte[] b, int off, int len) defineClass(String name, byte[] b, int off, int Len) converts the contents of byte array B into a Java Class and returns an instance of java.lang.Class.
-
FindClass (String name) finds the Class named name and returns an instance of the java.lang.Class Class.
-
LoadClass (String name) loads a Class named name and returns an instance of the java.lang.Class Class.
-
resolveClass(Class<? > c) link to the specified Java class.
Of these, the loadClass method is the most commonly involved.
The code is as follows:
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); if (c == null) { long t0 = System.nanoTime(); try { if (parent ! = null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } 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; }}Copy the code
The main steps of this method are as follows:
- FindLoadedClass (name) is called to determine whether the current class loader has loaded the class.
- If not loaded. The parent loader of the current ClassLoader is null. If it is not null, the parent loader is delegated to load. If null, Bootstrap ClassLoader is used for loading.
- If neither the parent loader nor Bootstrap ClassLoader can load, the findClass(name) method is called to find the class to load.
In addition, the loadClass method involves locking, using ConcurrentHashMap to lock different fully limited class names.
See the getClassLoadingLock method for details.
Four: parental delegation mode
The Java class loading mechanism uses the parent delegate pattern.
When a ClassLoader loads a class, it first delegates the task to its parent loader until Bootstrap ClassLoader.
If the parent loader does not load the class, it is returned layer by layer to the delegate initiator, the current ClassLoader, for loading.
In normal applications, users do not customize class loaders.
Class loading is first initiated by App ClassLoader, then delegated to Extension ClassLoader, and finally delegated to Bootstrap ClassLoader.
First, take a look at the classes loaded by the three ClassLoaders and the parent delegate pattern through an example.
Create a new JAR package called caxi-cl.jar with a simple Person class.
Write a simple program that outputs the ClassLoader corresponding to the Person object.
import com.acai.Person; public class TestClassLoader { public static void main(String[] args) { Person person = new Person(); System.out.println(person.getClass().getClassLoader()); }}Copy the code
Test one: Introduce jar packages into the project
The JAR package introduces the project
Corresponding output:
sun.misc.Launcher$AppClassLoader@18b4aac2
As you can see, classes in the ClassPath are loaded by the App ClassLoader.
Test 2: Copy the JAR package to the %JRE_HOME%/lib/ext directory
JRE_HOME is copied to the % % / lib/ext
Corresponding output:
sun.misc.Launcher$ExtClassLoader@4cc77c2e
The Extension ClassLoader is responsible for loading classes in the %JRE_HOME%/lib/ext directory.
When you load the Person class, you first try to load it using the App ClassLoader.
Because of the parent delegate mode, the Extension ClassLoader is finally delegated to the Extension ClassLoader, and the Person class exists under the directory %JRE_HOME%/lib/ext, so the class loading operation is performed.
Test 3: Append the JAR package to the Bootstrap ClassLoader loading path
Append to the Bootstrap ClassLoader loading path
Append the JAR package to the Bootstrap ClassLoader loading path using the -xbootclasspath /a:d:\acai-cl.jar argument.
Corresponding output:
null
As you can see, the loading of the Person class is eventually delegated to the Bootstrap ClassLoader.
Next, the parent delegate schema is verified with debug.
It’s the same simple demo.
import com.acai.Person; public class Test { public static void main(String[] args) { Person person = new Person(); System.out.println(person.getClass().getClassLoader()); }}Copy the code
Break a breakpoint on the loadClass method of the ClassLoader class.
App ClassLoader tried to load the file. Procedure
The Extension ClassLoader attempted to load
Bootstrap ClassLoader Attempts to load
As you can see, the loading process of the class conforms to the bottom-up delegation and will eventually be delegated to the Bootstrap ClassLoader.
Also consistent with loading from top to bottom, each layer of ClassLoader will try to load. Finally, the Person class is loaded by the App ClassLoader.
Next, try loading a special class: Splash. Class.
The Splash class is located in jfxrt.jar, which is in the %JRE_HOME%/lib/ext directory.
import com.sun.javafx.applet.Splash; public class ExtTest { public static void main(String[] args) { Splash splash = new Splash(null); System.out.println(splash.getClass().getClassLoader()); }}Copy the code
Corresponding output:
sun.misc.Launcher$ExtClassLoader@330bedb4
Without a doubt, the Splash class should be loaded by the Extension ClassLoader.
However, the loading process will still start from the default system ClassLoader App ClassLoader.
You can view the information through the DEBUG.
App ClassLoader tried to load the file. Procedure
The loading process of the Splash class is delegated to the Bootstrap ClassLoader, but the Bootstrap ClassLoader is not responsible for loading the classes in the %JRE_HOME%/lib/ext directory. The Extension ClassLoader will eventually load it.
The Bootstrap ClassLoader failed to load the file. Procedure
The Extension ClassLoader eventually loads it
Many articles on the relationship between the three classLoaders will give a demo of the getParent operation.
Bootstrap ClassLoader is considered the parent of the Extension ClassLoader.
Extension ClassLoader is the parent loader of App ClassLoader.
App ClassLoader is the parent of the custom ClassLoader.
This explanation is basically correct, but the relationship between Bootstrap ClassLoader and Extension ClassLoader requires additional explanation.
Parent delegation mechanism, picture from reference 7
The parent of the Extension ClassLoader cannot be specified as Bootstrap ClassLoader because the Bootstrap ClassLoader is not written in Java.
This relationship is compensated for in the loadClass method of the ClassLoader.
When loading a class, the system checks whether the parent loader of the current ClassLoader is null. If the parent loader is null, Bootstrap ClassLoader is used to load the class.
A null parent loader can only be an Extension ClassLoader.
For this procedure, see the loadClass method of ClassLoader.
Why use parental delegation?
There are many examples on the web about the String class. Assuming you write a java.lang.String class, using parental delegation prevents this problem.
But the parent delegate pattern can be broken, and what really prevents customizing java.lang.String is the “security mechanism.”
Here we try to customize the java.lang.String class and load it using a custom ClassLoader.
package java.lang;
public class String {
}
Copy the code
import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; public class StringClassLoader extends ClassLoader { @Override public Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { if ("java.lang.String".equals(name)) { return findClass(name); } else { return super.loadClass(name); } } @Override public Class<? > findClass(String s) throws ClassNotFoundException { try { byte[] classBytes = Files.readAllBytes(Paths.get("d:/String.class")); return defineClass(s, classBytes, 0, classBytes.length); } catch (IOException e) { throw new ClassNotFoundException(s); } } public static void main(String[] args) throws ClassNotFoundException { StringClassLoader stringClassLoader = new StringClassLoader(); Class clazz = stringClassLoader.loadClass("java.lang.String", false); System.out.println(clazz.getClassLoader()); }}Copy the code
This custom classloader breaks the parent delegate mechanism, as explained in the next section.
The output is:
Exception in thread “main” java.lang.SecurityException: Prohibited package name: java.lang
You can see that there is a section in defineClass called by the findClass method:
if ((name ! = null) && name.startsWith("java.")) { throw new SecurityException ("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); }Copy the code
It checks whether the fully qualified class name of the currently loaded class starts with Java. This is also a security mechanism.
If java.lang.String is loaded by the Bootstrap ClassLoader, the custom ClassLoader in the demo will be skipped and no exception will be generated.
So, the parent delegate pattern only prevents classes from being reloaded.
Five: Customize ClassLoader
In most cases, Java’s default three classloaders will suffice.
Custom class loaders can fulfill additional requirements, such as:
- Load classes from network files.
- Load classes from any directory.
- The bytecode file is encrypted and decrypted by a custom class loader.
The main steps to implement a custom class loader are:
- Inherit the ClassLoader class. If you only load classes from a directory or jar package, you can also choose to inherit the URLClassLoader class.
- Override the findClass method.
- In the overridden findClass method, the byte array corresponding to the class file is retrieved using either method and then converted into an instance of the class by calling the defineClass method.
What’s really fun about custom classloaders is that they break the parental delegation mechanism, and that’s what many interviewers ask.
As mentioned above, the parent delegate mode of class loading implements the loadClass method in the ClassLoader. To break this mechanism, you need to override the method.
Breaking the model of parental entrustment has some practical value.
For example, there are two class files, or two JAR packages.
Two of these classes have the same all-restricted class name, so if you want to use both classes together, you need to break the parent delegate pattern.
There are two Person classes with a fully qualified class name of com.acai.person, the only difference being that the sayHello() method outputs slightly different content.
package com.acai; import lombok.Data; @Data public class Person { private String name; private Integer age; public void sayHello() { System.out.println("Hello, this is Person in acai-cl"); }}Copy the code
package com.acai; import lombok.Data; @Data public class Person { private String name; private Integer age; public void sayHello() { System.out.println("Hello, this is Person in acai-cl2"); }}Copy the code
Jar the projects of the two Persons.
Two jars
As a general practice, introduce both JAR packages into the project.
Write a little demo.
import com.acai.Person; public class Main { public static void main(String[] args) throws Exception { Person person = new Person(); System.out.println(person.getClass().getClassLoader()); person.sayHello(); }}Copy the code
The corresponding output is:
sun.misc.Launcher$AppClassLoader@18b4aac2
Hello, this is Person in acai-cl
As you can see, the Person class in the Caci-cl.jar is used by default in demo.
If you want to use the Person class in caxi-cl2.jar, you want to create a new ClassLoader.
If you need to load classes from jar packages, URLClassLoader is preferred.
import com.acai.Person;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws Exception {
Person person = new Person();
System.out.println(person.getClass().getClassLoader());
person.sayHello();
URL url = new File("d:/acai-cl2.jar").toURI().toURL();
URLClassLoader loader = new URLClassLoader(new URL[]{url});
Thread.currentThread().setContextClassLoader(loader);
Class<?> clazz = loader.loadClass("com.acai.Person");
System.out.println(clazz.getClassLoader());
Method method = clazz.getDeclaredMethod("sayHello");
method.invoke(clazz.newInstance());
}
}
Copy the code
Corresponding output:
sun.misc.Launcher$AppClassLoader@18b4aac2 Hello, this is Person in acai-cl
sun.misc.Launcher$AppClassLoader@18b4aac2
Hello, this is Person in acai-cl
You can see that even if you specify the use of the caxi-cl2.jar, the output is still the sayHello of the Person in the caxi-cl. jar.
The reason is that both Person classes have the same all-restricted class name.
When loading the second Person, the App ClassLoader, the parent of the custom ClassLoader, has already loaded com.acai.Person.
So return the class directly, which is the Person class in the Caxi-cl.jar.
Create a new ClassLoader, break the parent delegate mechanism, and reload the class method.
import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandlerFactory; public class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } public MyClassLoader(URL[] urls) { super(urls); } public MyClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { super(urls, parent, factory); } @Override public Class<? > loadClass(String name) throws ClassNotFoundException { if (name.equals("com.acai.Person")) { return super.findClass(name); } else { return super.loadClass(name); }}}Copy the code
The loadClass method is overridden in MyClassLoader to call the findClass method directly when the loaded class name is equal to com.acai.Person, bypassing the parent delegate mechanism.
An if judgment is needed to say that the parent delegate is broken only when com.acai.Person is loaded.
When a class is loaded, its parent class is loaded at the same time.
The parent class of Person is java.lang.object.
Loading the Object class directly with a custom class loader throws a SecurityException.
So let’s write a demo.
import com.acai.Person; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class Main { public static void main(String[] args) throws Exception { Person person = new Person(); System.out.println(person.getClass().getClassLoader()); person.sayHello(); URL url = new File("d:/acai-cl2.jar").toURI().toURL(); URLClassLoader loader = new URLClassLoader(new URL[]{url}); Thread.currentThread().setContextClassLoader(loader); Class<? > clazz = loader.loadClass("com.acai.Person"); System.out.println(clazz.getClassLoader()); Method method = clazz.getDeclaredMethod("sayHello"); method.invoke(clazz.newInstance()); URL url2 = new File("d:/acai-cl2.jar").toURI().toURL(); MyClassLoader myClassLoader = new MyClassLoader(new URL[]{url2}); Class<? > clazz2 = myClassLoader.loadClass("com.acai.Person"); System.out.println(clazz2.getClassLoader()); Method method2 = clazz2.getDeclaredMethod("sayHello"); method2.invoke(clazz2.newInstance()); }}Copy the code
sun.misc.Launcher$AppClassLoader@18b4aac2 Hello, this is Person in acai-cl
sun.misc.Launcher$AppClassLoader@18b4aac2 Hello, this is Person in acai-cl
MyClassLoader@5e2de80c
Hello, this is Person in acai-cl2
As you can see, the Person class in caxi-cl2.jar is loaded correctly.
It is concluded that the parent delegation mechanism can be destroyed by rewriting loadClass by customizing ClassLoader.
Six: reference materials
[1] Java Garbage Collection Basics [2] How does Java ClassLoader load itself into memory for execution? [3] In-depth analysis of Java ClassLoader mechanism [4] In-depth analysis of Java ClassLoader principle [5] In-depth analysis of Java ClassLoader mechanism [6] In-depth analysis of Java ClassLoader mechanism (source level) [7] Java class loading principle and ClassLoader use summary [8] implementation of Java ClassLoader dynamic loading JAR package [9] ClassLoader basic details