Recent interview asked parents to delegate, for parents to delegate and undermine their parents delegate mechanism before understanding in the deep understanding of Java virtual machine, then feel quite a simple concept, but the interviewer questions carefully found himself just the there is some misunderstanding, this problem can be talked about 20 minutes, Of course, the nature after the interview did not below 😂.

The story begins with this CLASS UML diagram of a parent delegate model:

  • Error 1: at that time looked at the class loader model diagram, thought to start the class loader, expand the class loader, the application class loader is inheritance relationship with custom accumulator, actually “parent” and “loader” is the combination relationship, this father and son and loader actually naming conflicts bring to people’s subjective idea;
  • Myth # 2: Not being clearfindClass()withloadClass()The relationship between the two methods and the specific role of the two methods, break the parent delegate mechanism and follow the parent delegate mechanism and the correlation of the two methods;
  • Myth # 3: Because you don’t use it at workClassLoader, not clear in JavaClassLoaderClass andBootstrap Class Loder,Extension Class Loader,Application Class LoaderThe relationship between; It is wrong to follow the class loader model diagram and think that your own class loader needs to inherit from the application class loader to implement a custom class loader.

The most important reason for the formation of misunderstandings is that when they read “in-depth understanding of the Java Virtual Machine”, in fact, or in the state of knowledge reserve, the relevance between knowledge is not in place, so today to reorganize ~

Parent delegation model

What is the parent delegation model? The parent delegate model defines how the loader loads a class. That is, when a loader receives a request to load a class, it first delegates to the parent loader to handle the loading. Only when the parent loader fails to load the class, the current loader will load it.

So when is the class loader triggered to load the class? When an active reference to a class occurs in a program, the loading of the referenced class is triggered if the current class has not been loaded into the method area.

What does the classloader need to do after triggering the loading of the class? The VIRTUAL machine through the class loader needs to do the following three things during the Java class loading phase:

  • Gets the binary byte stream that defines a class through its fully qualified name
  • Converts the static storage structure represented by the byte stream into the runtime data structure of the method area
  • Generate an in-memory representation of the classjava.lang.ClassObject that serves as an entry point for the various data of the class in the method area.

So when does the parent loader fail to load a class? Because each loader is responsible for loading a different class:

  • To start the class loader:The main load<JAVA_HOME>\libDirectory, and matched by filename, such asrt.jar,tools.jarThe Java core class library; This class passesC++Language implementation, is a part of the virtual machine;
  • Extending the class loader:The main load<JAVA_HOME>\lib\extExtension classes in directories; throughJavaLanguage, the corresponding class issun.misc.Launcher$ExtClassLoader;
  • Application class loaders:Primarily load the user classpath (classpath); throughJavaLanguage, the corresponding class issun.misc.Launcher$AppClassLoader, can be passedClassLoaderIn the classgetSystemClassLoader()Method to get a reference to the application class loader, and the default class loader will be used if there is no custom class loader in the application;

Parent delegation model implementation

After JDK1.2, we implement the parent delegate model by using a ClassLoader::loadClass(), and we also use the loadClass() method to load classes:

Public abstract class ClassLoader {// The parent that the current ClassLoader depends on. private final ClassLoader parent; // When building the classLoader, // getSystemClassLoader() returns AppClassLoader by default This (checkCreateClassLoader(), null, PlatformClassLoader protected ClassLoader(); getSystemClassLoader()); } protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. First check whether the current Class Class<? > c = findLoadedClass(name); If (c == null) {long t0 = system.nanotime (); try { if (parent ! = null) {c = parent.loadclass (name, false); } else {// 2.2 if the parent loader is null, use the bootloader to load c = findBootstrapClassOrNull(name); }} catch (ClassNotFoundException e) {if (c == null) {long t1 = system.nanotime (); // if you want to follow the parent delegate mechanism, The child loader needs to implement its own load logic by overriding the findClass() method c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }}}Copy the code

Why follow the parent delegate model?

The main reason is that the parent delegation model is used to organize the relationship between class loaders, so that classes in Java have a hierarchical relationship with priority along with their class loaders, and this relationship ensures the security of class loading.

If the user defines an Object Class, the parent delegate model will first load the Object Class by starting the Class loader, and then the obejct. Class will naturally be loaded in the

lib directory. Instead of loading the Object class defined by the user in the classpath, this ensures that the Java core class library classes will not be incorrectly loaded.

Break the parent delegation model

Unfortunately, the parent delegation model is not a perfect solution to all problems in a complex world:

Upper layer components depend on lower layer components:

JDBC is the most common one, because the databases used in our project may be Oracle, MySQL, SQL Server, etc., and different databases provide different JDBC drivers due to different usage modes. In this way, application code can add, delete, modify and check different data by calling JDBC Driver Interface.

Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
Copy the code

However, the DriverManager class used to obtain database connections is located under rt.jar, and should be loaded via the startup class loader according to the parent delegate model. However, this class depends on the Driver class implemented by a different third party because of the database type used by the project. On the other hand, the implementation classes are generally placed in the user classpath, which naturally cannot be loaded by the startup class loader.

JDK1.6 solves this problem by introducing ServiceLoader, a special class loader based on ThreadContextClassLoader:

Public static <S> ServiceLoader<S> load(Class<S> service) {// AppClassLoader ClassLoader cl = Thread.currentThread().getContextClassLoader(); / / by users rely on the classpath AppClassLoader loading the upper component under the package return new ServiceLoader < > (Reflection) getCallerClass (), service, cl); }Copy the code

However, improper use of thread context can lead to memory leaks, as described in this article: thread ContextClassLoader memory leak

Lack of isolation problems:

For Tomcat with multiple Web containers running on a SINGLE JVM, inadequate class visibility can lead to a lack of reasonable isolation between Web containers, resulting in security and dependency component version conflicts between containers, and an inability to support class hot change.

  • Security issues: With parent delegation, all classes and libraries in the CLASSPATH specified path in the same JVM will be loaded by AppClassLoader. Therefore, classes in the CLASSPATH path can be loaded and used by all Web containers running on the JVM through the AppClassLoader class loader. But for each Web application running on the JVM (servlet container), the servlets in the container should only have access to their own WEB-INF/classes and classes in the WEB-INF/lib directory; If the servlet is loaded through the AppClassLoader class loader, then the servlet can access other classes in the CLASSPATH that are not in the container through the AppClassLoader, which clearly violates the class isolation required by the servlet container.

  • Version conflict: Multiple modules of a project may have dependencies on different versions of the same third-party component. According to the parent delegation model, if the AppClassLoader is used as the class loader to load the dependent classes of each module, only one version of the third-party component will be loaded in the CLASSPATH path. PS: Similarly, multiple versions of a package are introduced directly or indirectly through Maven, leading to version conflicts. This is also solved through the custom classloader model.

  • Hot revision is not supported: according to the parent delegation mechanism, different modules depend on the same AppClassLoader, so there is a coupling relationship between the modules to the loaded classes. That is, we can not uninstall and replace a class at will, because we don’t know whether other modules depend on the third party components of this version.

Solution:

To address a number of issues caused by the lack of isolation between containers, Tomcat has customized its own classloader model:

The Tomcat container customizes a WebappClassLoader that breaks the parent delegation model by copying the ClassLoader::loadClass() method and associated a WebappClassLoader for each Web application. Thus, the isolation of special dependent classes among Web applications is realized. That is, each application will first load its own web-INF /classes and web-INF /lib dependent class libraries through its own class loader. The class is loaded by the upper class loader only if the required class is not loaded (breaking the rule that the parent delegate is loaded by the parent class first);

However, there may also be some common dependencies between each Web application of Tomcat, such as Servlet specification related packages and some tool class packages, so the Class loader model of Tomcat loads the jar packages shared between Web applications through the parent loader SharedClassLoader.

According to the class loader model, the implementation of hot loader only needs to dynamically replace the WebAppClassLoader of the Web container, which avoids the loss caused by restarting the entire Java project and does not need to worry about whether the classes loaded by the replaced class loader will be referenced by Web applications running on the same JVM.

Concrete code implementation

In addition to the purpose of defining the class loader model mentioned above, the Tomcat custom class loader also implements the caching of loaded classes and the preloading of classes.

Public abstract class WebappClassLoaderBase extends URLClassLoader {// ClassLoader protects the final memory cache for loaded classes Map<String, ResourceEntry> resourceEntries = new ConcurrentHashMap<>(); @override // @param: name public Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<? > clazz = null; // 1.1 WebAppClassLoader Map cache clazz = findLoadedClass0(name); if (clazz ! = null) { return clazz; } clazz = findLoadedClass(name); if (clazz ! = null) { return clazz; String resourceName = binaryNameToPath(name, false); ClassLoader javaseLoader = getJavaseClassLoader(); boolean tryLoadingFromJavaseLoader; // Try to use the parent of AppClassLoader to load the JavaSE // To avoid the custom classes in the Tomcat class overwrite the core class URL URL = javaseLoader.getResource(resourceName); tryLoadingFromJavaseLoader = (url ! = null); } catch (Throwable t) { tryLoadingFromJavaseLoader = true; } if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz ! = null) { return clazz; }} catch (ClassNotFoundException e) {// Ignore}} // Pass the fully qualified name of the loading class to the filter() method to determine whether to delegate to the parent loader. Boolean delegateLoad = delegate || filter(name, true); // 2.2 If delegate loading is required, If (delegateLoad) {try {// Delegate the class to the parent AppClassLoader clazz = Class.forName(name, false, parent); if (clazz ! = null) { return clazz; }} catch (ClassNotFoundException e) {// Ignore}} // 2.2 Break the parent delegate mechanism, // The actual class loading method is implemented by its own findClass() method. // As you can see, the loadClass() method is generally used to implement the class loader model. Try {clazz = findClass(name); if (clazz ! = null) { return clazz; }} catch (ClassNotFoundException e) {// Ignore} // 2.3 If there is no Class in the current Web application Class directory // then use the parent loader to make the final loading attempt according to the parent assignment model if (! delegateLoad) { try { clazz = Class.forName(name, false, parent); if (clazz ! = null) { return clazz; }} catch (ClassNotFoundException e) {// Ignore}}}} Throw new ClassNotFoundException(name); }}Copy the code

Summary and Reference (answer misunderstanding)

  1. The classloader’s parent delegate model passesClassLoader::loadClass()Methods The parent loader was loaded first by combining the parent loader.
  2. LoadClass () is generally used to implement class loader models, such as parent delegate model, Tomcat class loader model, etc. FindClass () is used to define the specific method of class loading for the current class loader under the current class loader model;
  3. Implement the class loader yourself, and if you follow the parent delegate model, you only need to copy itfindClass()Method can be; If you need to break the parent delegate model, you need a carbon copyloadClass()withfindClass().

Main reference:

Understanding the Java Virtual Machine Edition3

Are you sure you really understand “parental delegation”? !

How to write a hot load by hand

The principle and application of The Java Classloader