This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.


theme: juejin highlight: a11y-dark

I. Introduction and analysis of parental delegation mechanism

The JVM when the class is loaded, the default is parents delegate mechanism, popular, is a specific type of class loader in receive the loading class’s request, the first loading the mission entrusted to the parent class loader, in turn, recursive (essentially recursive calls loadClass function), so all the request will eventually transmitted to the top in the start of the class loader. If the parent class loader can complete the load request, it returns success; If the parent class loader is unable to complete the load request, the subclass will attempt to load itself. In fact, in most cases, the more basic classes are loaded by the upper loader, because they tend to be called by user code frequently (there are also cases where the base class calls back to user code, breaking the parent delegate mechanism). Next, we will briefly analyze the default parent delegate mechanism of virtual machines using system class loaders and extended class loaders as examples.

ExtClassLoader inheritance diagram:

AppClassLoader inheritance diagram:

The inheritance diagram of the extension ClassLoader and the system ClassLoader shows that both inherit from the java.lang.ClassLoader abstract class. So here are some important methods for ClassLoader:

// load the binary type of the specified Class name for the user to call public Class<? > loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } // save the binary type of the Class name that specifies whether to resolve (resolve may not be resolved). > loadClass(String name, Boolean resolve) throws ClassNotFoundException{// ToDo} // findClass is normally called by the loadClass method to load the named Class. > findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } // The JVM has implemented the specific function of generating the corresponding internal data structure to be placed in the method area, so there is no need to overwrite. protected final Class<? > defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ return defineClass(name, b, off, len, null); }Copy the code

In standard extension class loader ExtClassLoader and the system class loader AppClassLoader and both the parent class (java.net.URLClassLoader and Java. Security. SecureClassLoader) code, Neither delegate the loadClass method of the loading rule in java.lang.ClassLoader. Therefore, we can analyze the principle of virtual machine default parent delegate mechanism from the loadClass method in the source code of ClassLoader

public Class<? > loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected Class<? > loadClass(String name, Boolean resolve) throws ClassNotFoundException {// Obtain synchronized (getClassLoadingLock(name)) {// Check whether the class has been loaded Class<? > c = findLoadedClass(name); // If (c == null) {long t0 = system.nanotime (); Try {// If there is a parent loader, delegate to the parent loader to load if (parent! = null) { c = parent.loadClass(name, false); } else {// call the local method findBootstrapClass() BootStrap class loader load c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent Class loader} // If (c == null) {long t1 = system.nanotime (); C = findClass(name); // Call the defineClass method. // 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

Is it true that the parent of the system class loader is the extension class loader and the parent of the extension class loader is the start class loader? Let’s test this with code:

/ * * * < p > * the relationship between the test class loader * < / p > * * @ Author: Liziba * @ the Date: 2021/5/31 21:14 */ public class ClassLoaderRelationshipTest { public static void main(String[] args) { System.out.println(ClassLoader.getSystemClassLoader()); System.out.println(ClassLoader.getSystemClassLoader().getParent()); System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); }}Copy the code

Output result:

Through the test code and output the result above, can see very clear. This getSystemClassLoader () can directly obtain the system class loader, By this. GetSystemClassLoader (.) getParent () can be seen that the system class loader’s parent class loader is an extension class loader, But this getSystemClassLoader (). The getParent (). The getParent () output is null, if our guess is that there is a problem? In fact, since the startup class loader cannot be obtained directly from Java code, it is implemented in a virtual machine, and the JVM defaults to NULL to represent the startup class loader. This point is known from the ClassLoader constructor.

// parent is set to private and does not provide setters. Private final ClassLoader parent; GetSystemClassLoader () {this(checkCreateClassLoader(), getSystemClassLoader()); } // enforce the parent ClassLoader protected ClassLoader(ClassLoader parent) {this(checkCreateClassLoader(), parent); } private ClassLoader(Void unused, ClassLoader parent) {this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; }}Copy the code

\

Example of parent delegation mechanism

1. Create the test bean

package com.liziba.classloader.bean;

public class Person {

    private String name;

}
Copy the code

2. Create test classes in the current project

package com.liziba.classloader; Public class ClassLoaderRuleTest {public static void main(String[] args) {try {// Check the Java class path System.out.println(System.getProperty("java.class.path")); // Call the classloader that loads the current Class to load the test Class ClassLoaderRuleTest Class<? > clazz = Class.forName("com.liziba.classloader.ClassLoaderRuleTest"); System.out.println(clazz.getClassLoader())); } catch (ClassNotFoundException e) { e.printStackTrace(); }}}Copy the code

Output result:

Package person. class as test.jar and copy it to <JAVA_RUNTIME_HOME>\lib\ext

Run the code test again and see the output: sun.misc.Launcher$ExtClassLoader@7f31245a

The system class loader, when receiving a load request, first delegates the request to the parent class loader (the standard extension class loader). In the example above, the extension class loader loads the load request of class Person.class first.


4. Copy test.jar to <JAVA_RUNTIME_HOME>\lib

Output result:

The results of steps 4 and 3 are the same. The person. class load request is loaded by the extension class loader, which is not inconsistent with the parent delegate mechanism mentioned above. For security reasons, the JVM does not load unfamiliar classes that exist in the <JAVA_HOME>/lib directory, only classes specified by the JVM.

5, delete <JAVA_RUNTIME_HOME> lib\ext test.jar and person. class compiled in current directory

Output: system throws Java. Lang. ClassNotFoundException

Develop your own class loader

During class loading, the class loader that actually loads the class may not be the same class loader that initiates the loading process. The actual class loading is done by calling defineClass; The loading process of the startup class is implemented by calling loadClass. The first is called the defining loader of a class, while the latter is the initiating loader. When the Java virtual machine determines whether two classes are the same, it uses the class definition loader. That is, it doesn’t matter which classloader initiates the loading process for the class, but what matters is the loader that ultimately defines the class. Methods loadClass () throws is Java. Lang, a ClassNotFoundException exception; Method defineClass () throws is Java. Lang. NoClassDefFoundError exception. When a Class is successfully loaded, the Class loader caches the resulting instance of the Java.lang. Class Class. The next time the class is loaded, the class loader uses the cached instance of the class without attempting to load it again. That is, classes with the same full name are loaded only once for a classloader instance, meaning that the loadClass method is not called repeatedly.

File system class loader

package com.liziba.classloader; import java.io.*; / * * * < p > * file system class loader * < / p > * * @ Author: Liziba * @ the Date: 2021/5/31 23:04 */ public class FileSystemClassLoader extends ClassLoader {/** specifies a file path */ private String rootDir; public FileSystemClassLoader(String rootDir) { this.rootDir = rootDir; } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { byte[] data = getClassByteData(name); if (data == null || data.length == 0) { throw new ClassNotFoundException(); } else { return defineClass(name, data, 0, data.length); } /** * private byte[] getClassByteData(String className) {String path = classNameCovertToPath(className); try { InputStream in = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024 * 4]; int len = 0; while ((len = in.read(buffer)) ! = -1) { baos.write(buffer, 0, len); } return baos.toByteArray(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } /** * class permission name to absolute path ** @param className * @return */ private String classNameCovertToPath(String className) {return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class"; }}Copy the code

The test class

package com.liziba.classloader; import com.liziba.classloader.bean.Person; / * * * < p > * test file class loader * < / p > * * @ Author: Liziba * @ the Date: 2021/5/31 23:12 */ public class TestFileSystemClassLoader { public static void main(String[] args) { String rootDir = "E:\workspaceall\liziba-java\out\production\liziba-java"; String className = "com.liziba.classloader.bean.Person"; FileSystemClassLoader fscl = new FileSystemClassLoader(rootDir); Class<? > clazz = null; try { clazz = fscl.findClass(className); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); }}}Copy the code

Output result:

\

2. Network class loader

package com.liziba.classloader; import sun.nio.ch.Net; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URL; / * * * < p > * network class loader * < / p > * * @ Author: Liziba * @ the Date: 2021/5/31 23:25 */ public Class NetworkClassLoader extends ClassLoader{/** specifies a network URL */ private String rootUrl; public NetworkClassLoader(String rootUrl) { this.rootUrl = rootUrl; } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { byte[] classData = getClassData(name); if (classData == null) { throw new ClassNotFoundException(); } else { return defineClass(name, classData, 0, classData.length); Private byte[] getClassData(String className) {String path = classNameCovertToPath(className); try { URL url = new URL(path); InputStream ins = url.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024 * 4]; int len = 0; While ((len = ins.read(buffer))! = -1) { baos.write(buffer, 0, len); } return baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } /** * class permission name to absolute path ** @param className * @return */ private String classNameCovertToPath(String className) {return rootUrl + "/" + className.replace('.', '/') + ".class"; }}Copy the code

Once the network class loader is loaded, there are generally two ways to use the class

  • Use the Java reflection API
  • Using an interface

The specific use process and the above file class loader use much the same, I believe that smart bulk also do not need to demonstrate!