Starting from the source code, I will take you to analyze the parental delegation of Java class loading

Java As one of the core features of Java, class loading has many features. Understanding and mastering the way and mechanism of class loading can better understand the execution process of the program. This article will introduce Java's class loading mechanism and its related necessary knowledge in detail.

Let's get right to it

1. File Compilation

Before introducing the class loader, first introduce  Java the basic compilation and execution commands of the file for subsequent use.

1. Class compilation

  • file compilation

     Compile the file into  a file through  javac the command   , and output to the current directory by default..java.class

     bash 

    copy code

    javac MyBean.java
  • encoding process

    When  Java the file contains encodings such as Chinese, you need to specify the encoding format when compiling, otherwise it will compile abnormally.

    bash

    copy code

    javac -encoding utf-8 MyBean.java
  • Package name processing

    The file compiled by the default  javac command does not contain the package path, and  -d the file generated by parameter compilation will create a package name hierarchy folder in the same level directory. It  . means to save to the current directory, and the directory can be specified as required.

    bash

    copy code

    javac -d . MyBean.java
  • specified version

    When there are multiple  JDK versions in the programming environment, you can specify the compiled version by entering the full path  JDK .

    bash

    copy code

    "C:\Program Files\Java\jdk1.8.0_202\bin\javac" -d <target_path> MyBean.java

2. File Execution

  • Class operation

    Class It is relatively simple to run the file, just pass  java +  directly 文件名 . It should be noted that if the package name is set when compiling the file, the package name also needs to be specified when running.

    bash

    copy code

    # 运行不含包名 class 文件 java MyBean # 运行含包名 class 文件 java xyz.ibudai.MyBean
  • jar run

    Running  Jar a file is similar to running  Class a file, just add  -jar parameters.

    bash

    copy code

    java -jar jar-name.jar

Second, the class loader

1. Basic categories

Java The class loaders in  China are divided into the following four categories, and the descriptions of each type of loader are as follows:

Loader describe
BootstrapClassLoader The top-level loader is mainly responsible for loading core class libraries (java.lang.*, etc.).
ExtClassLoader It is mainly responsible for loading some extended JAR packages under the jre/lib/ext directory.
AppClassLoader It is mainly responsible for loading the main function main of the application program, etc.
CustomClassLoader The custom class loader, that is, we inherit ClassLoad and the custom loader belongs to the bottom loader.

2. Loading method

 There are  two ways Java to load classes in   which are introduced below.Class.forName()Classloader.laodClass()

  • Class.forName()

    Loading by  method will not only load the current class into the memory but also initialize the object. You can add  a static block print prompt  Class.forName() to the class to be loaded. When  using  the loaded class, you can find that the static block code will be executed, indicating that it is created at this time. object.staticClass.forName()

    If you want to implement class loading without creating objects, you can use  Class.forName(className, false, classLoader) method loading, where the second parameter is used to specify whether to create objects, and the third parameter specifies the class loader.

     java 

    copy code

    public void initDemo2() { String className = "xyz.ibudai.bean.User"; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { Class<?> clazz1 = Class.forName(className); Class<?> clazz2 = Class.forName(className, false, classLoader); System.out.println("clazz1 loader: " + clazz1.getClassLoader()); System.out.println("clazz2 loader: " + clazz2.getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
  • Classloader.laodClass()

    Loading by  laodClass() means does not resolve the class, only implements loading and does not create the corresponding object, and is only initialized when the object is referenced.

    java

    copy code

    public void initDemo2() { String className = "xyz.ibudai.bean.User"; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { Class<?> clazz3 = classLoader.loadClass(className); System.out.println("clazz3 loader: " + clazz3.getClassLoader()); } catch (Exception e) { e.printStackTrace(); } }

It should be noted  java.lang.String that the core class library is  JVM loaded at startup, they are located  Java in the core class library of the virtual machine, and are  Bootstrap ClassLoader loaded by . Therefore, when obtaining the class loaders of these classes, the result is usually  null , it should be noted that  null the class loader is unknown or cannot be determined, rather than that there is no class loader.

3. Class loading mechanism

1. Loading mechanism

The class loader is responsible for loading the bytecode of classes from different sources (such as file system, network, memory, etc.), such as the most common  .class files, and converting them into  Java objects so that programs can use these classes.

The class loader is also responsible for resolving class dependencies, that is, finding and loading other classes that the current class depends on, and determining which class loader should load each class.

Java The class loading mechanism has the following characteristics:

  • Lazy loading : A class is only loaded when it is needed to save memory and loading time.
  • Parental delegation : The class loader will load classes according to the hierarchy, that is, first entrust the parent class loader to load, and if the parent class loader cannot load, then hand it over to itself to load.
  • Caching mechanism : Classes that have already been loaded will be cached to avoid loading the same class repeatedly.
  • Destroy the parental delegation mechanism : Allow users to customize the class loader to load classes, so that some custom class loading strategies can be implemented, such as hot deployment, plug-in, etc.

2. Loading process

The so-called class loading process, in simple terms, the compiled  class file is loaded into the virtual machine memory in a specific way  JVM , and then it should be able to be read in the virtual machine memory.

The class loader mainly goes through three stages to load bytecode files into memory  加载 -> 连接 -> 实例化.

Note that it is  .class not loaded into memory all at once, but  Java only when the application needs it. That is to say, when  JVM a class is requested to be loaded, the class loader will try to find and locate the class, and after finding the corresponding class, load its fully qualified class definition into the runtime data area.

4. Appointment by parents

The parent delegation mechanism is a very important type of loading mechanism in class loading, through which the safety and integrity of the class are guaranteed.

JDK The parental delegation mechanism is used by default. You can  find  java.lang the  ClassLoader class under the package. The following is an analysis of the parental delegation mechanism from the perspective of source code.

1. Method description

Go through the table below before parsing the core source code. Each method will be involved later. Here is a brief description of its function and will not be described in detail later.

method effect
getClassLoadingLock() Prevent classes from being loaded concurrently.
findLoadedClass() Determine whether the class has been loaded, and return the loader if it has been loaded, otherwise return null.
findBootstrapClassOrNull() Delegate the parent loader to load, if no parent loader exists, return null.
findClass() Read the .class file based on the full class name and call defineClass() to find the corresponding java.lang.Class object.
defineClass() Convert byte stream to java.lang.Class object, byte stream can come from .class file and other ways.
resolveClass() Bytecode files are loaded into the JVM during class loading and are not immediately converted into java.lang.Class objects. Only when using this class will resolveClass() be called for conversion, and operations such as creating an instance can be performed.

2. Interpretation of source code

The core of the class loading implementation is  loadClass() the method, which uses  synchronized keywords to ensure that the class will not be loaded repeatedly. The source code is as follows  JDK ,  loadClass() and I have provided comments at the key points.

Among them  parent is the parent loader of the current loader.

 
 

java

copy code

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 1. 判断类是否已经加载过 Class<?> c = findLoadedClass(name); // 1.1 若不为空表明已加载则返回 if (c == null) { try { // 2. 未加载 -> 判断是否存在父加载器 if (parent != null) { // 2.1 存在 -> 递归调用直至获取顶层父加载器 c = parent.loadClass(name); } else { // 2.2 不存在(已达顶层加载器) -> 委托父类进行加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found } // 3. 没有父加载器可以加载类 -> 由类自身实现加载 if (c == null) { c = findClass(name); } } return c; } }

5. Custom loading

There are two most common types of custom class loaders, following the parental delegation mechanism and breaking the parental delegation mechanism. The latter is mainly discussed here.

1. Parent class inheritance

Create a new custom loader class  CustomClassLoader and inherit it  , and define  basic information such as ClassLoader the construction method and the default file storage path in the class  .  The and  classin the constructor   are used to specify the parent loader of the current loader. The former defaults to obtain the class loader of the current context as the parent loader, in most cases   .super()super(parent)AppClassLoader

In practical applications, usually only specific classes need to implement custom loading, so the collection is defined here to  targetList store the target classes that need to implement custom loading, and the default loading method is used for classes that are not in this collection.

 
 

java

copy code

public class CustomClassLoader extends ClassLoader { private final Path classDir; private static final Path DEFAULT_CLASS_DIR = Paths.get("E:\\Workspace\\Class"); private List<String> targetList = new ArrayList<>(); static { targetList.add("xyz.ibudai.bean.TestA"); targetList.add("xyz.ibudai.bean.TestB"); } /** * 使用默认的信息 */ public CustomClassLoader() { super(); this.classDir = DEFAULT_CLASS_DIR; } /** * 指定文件目录与父类加载器 */ public CustomClassLoader(String path, ClassLoader parent) { super(parent); this.classDir = Paths.get(path); } }

2. loadClass()

The next step is also the focus of custom class loading.  JDK The core process of loading a class is  loadClass() controlled by a method. If you want to break the default parent delegation mechanism, you must rewrite this method.

The rewritten  loadClass() implementation process of its concrete loading is as follows:

  • Check if the current class has already been loaded?

    • If loaded, return the result.
    • If not loaded, go to the next step.
  • Is the loaded class in the target collection?

    • If so, implement custom  .class file reading and loading.
    • If not, call  loadClass() the loading retry of the parent loader, that is, still follow the parental delegation mechanism.
  • Determine whether the custom loading is successful?

    • If so, return the result.
    • If not, call  loadClass() the loading retry of the parent loader, that is, still follow the parental delegation mechanism.

The code implementation corresponding to the above logic flow is as follows:

 
 

java

copy code

@Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { Class<?> c = findLoadedClass(name); if (c != null) { // 若已加载则返回 return c; } if(targetList.contains(name)) { try { // 若在目标集合则自定义加载 c = customFindClass(name); } catch (ClassNotFoundException ignored) { // 加载异常重新委派父类加载 c = super.loadClass(name, resolve); } } if (c == null) { // 加载不成功重新委派父类加载 c = super.loadClass(name, resolve); } return c; } }

3. findClass()

loadClass() It is used to control the loading process of the class  in the loading of the class  , and findClass() it is used to control the specific  .class file loading implementation, that is, how to  .class load it into the file  JVM .

Here I chose to create a new  customFindClass() method instead of rewriting.  The purpose is to prevent  confusion and conflicts when calling the parent class  when  findClass() the custom class fails to load  .loadClass()findClass()

customFindClass() Loading the compiled class file is realized in  JVM that is, the compiled  .class file is read from the local and converted into byte data and  defineClass() the corresponding  java.lang.Class object is searched.

where  name is the full class name of the class file, for example: xyz.ibudai.bean.TestA .

 
 

java

copy code

private Class<?> customFindClass(String name) throws ClassNotFoundException { // 读取 class 的二进制数据 byte[] classBytes = this.readClassBytes(name); if (classBytes == null || classBytes.length == 0) { throw new ClassNotFoundException("The class byte " + name + " is empty."); } // 调用 defineClass 方法定义 class return this.defineClass(name, classBytes, 0, classBytes.length); } /** * 将 class 文件转为字节数组以供后续内存载入 */ private byte[] readClassBytes(String name) throws ClassNotFoundException { // 将包名分符转换为文件路径分隔符 String classPath = name.replace(".", "/"); Path classFullPath = classDir.resolve(Paths.get(classPath + ".class")); if (!classFullPath.toFile().exists()) { throw new ClassNotFoundException("Class file " + classFullPath + " doesn't exists."); } try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { Files.copy(classFullPath, out); return out.toByteArray(); } catch (IOException e) { throw new ClassNotFoundException("Read class " + classFullPath + " file to byte error."); } }

4. Test example

After completing the above example, verify the effect of our class loading through an example.

Create a new test class  TestA and  java -d . TestA.java compile it into  .class a file through the command, and move the compiled  .class file with the package name hierarchy to  CustomClassLoader the default directory defined in the class.

 
 

java

copy code

public class TestA { public void sayHello() { System.out.println("Hello world!"); } }

CustomClassLoader After completing the above operations, write the corresponding test example, realize  TestA the loading of the class  through the default class loading and  two methods, and output TestA the specific loading class information respectively.

The corresponding test sample code is as follows:

 
 

java

copy code

@Test public void loadTest() throws Exception { // 默认类加载器加载类 Class<?> clazz1 = Class.forName("xyz.ibudai.bean.TestA"); System.out.println("Loader-1: " + clazz1.getClassLoader()); // 自定义类加载器加载类 CustomClassLoader myLoader = new CustomClassLoader(); Thread.currentThread().setContextClassLoader(myLoader); Class<?> clazz2 = myLoader.loadClass("xyz.ibudai.bean.TestA"); System.out.println("Loader-2: " + clazz2.getClassLoader()); }

The result of running the above program is as follows. From the result, we can see that  CustomClassLoader we have successfully implemented  TestA custom loading.

 
 

java

copy code

Loader-1: sun.misc.Launcher$AppClassLoader@18b4aac2 Loader-2: xyz.ibudai.loader.CustomClassLoader@1b9e1916

6. Advanced operation

1. Context Loader

In the previous example,  Thread.currentThread().getContextClassLoader() the class loading of the current context was passed, so what is the use of context class loading?

When  Java the program starts, the core class will  Bootstrap Classloader be loaded by the client, but there are some standard interfaces that are only defined by the service provider interface  (SPI) . For example, the most common  JDBC specific implementations are implemented by the major database manufacturers themselves. And this part of the interface implementation class obviously cannot be loaded by the system class loader (the system class loader will only load the core package, not the third-party source package), and the default class loading mechanism ( ) does not support the 双亲委派机制parent loader to delegate downward , the context loader is born for this purpose, and  SPI breaks the parental delegation mechanism by means of child delegation (the implementation class is loaded by the current thread context loader).

Java The context class loader of the initial thread when the application is running is the system class loader ( AppClassLoader), and the context class loader of the thread can be obtained and set through  Thread.currentThread().getContextClassLoader() the  Thread.currentThread().setContextClassLoader() method. If a thread is not specified, it will inherit its parent thread context class loader by default. .

2. Dynamic loading

Java Relevant classes will be loaded  at  startup, and URLClassLoader dynamic class loading during program running can be realized by passing through.

URLClassLoader Inherited from  ClassLoader, it can be loaded by  URL loading classes from resource files, such as by  URLClassLoader loading  JAR the driver package in the following example.

 
 

java

copy code

public static ClassLoader getClassLoader(String driverPath) { // 获取当前线程上下文加载器 ClassLoader parent = Thread.currentThread().getContextClassLoader(); URL[] urls; try { File driver = new File(driverPath); // 驱动包不存在抛出异常 if (!driver.exists()) { throw new FileNotFoundException(); } // File 转 URL 资源格式 list.add(driver.toURI().toURL()); urls = list.toArray(new URL[0]); } catch (Exception e) { throw new RuntimeException(e); } return new URLClassLoader(urls, parent); }

3. Class unloading

Although  URLClassLoader the dynamic loading of classes can be realized,  Java there is no explicit way to realize the unloading of classes. Unloading a loaded class can only be achieved through garbage collection.

If a class wants to be unloaded by garbage collection, it needs to meet the following three conditions:

  • There are no instances of the current class, that is, all object instances have been destroyed.
  • Reference ( Reference) does not exist for the current class.
  • The class load for the current class has been garbage collected ( GC).

Seven, class isolation

In the above, we introduced the loading method of classes and focused on the analysis of the parent delegation mechanism. Next, we will introduce the application scenarios of class loading.

1. Basic introduction

Similar to the thread context loader mentioned before (the child thread inherits the parent thread loader by default), the referenced class of a class will also be loaded by the class loader of the application class, that is, if it is referenced in , it will also  ClassA be  ClassB loaded  ClassB by  ClassA the Class loading is used to load, which is one of the realization principles of class isolation.

So what is the role of class isolation? As mentioned above, class loading is transitive by reference, and a class in a module will only be loaded once. When the class is loaded, it is judged  findLoadedClass() whether to load it, and if so, the loading is skipped, that is,  JVM each class in the virtual machine has and only There is one.

2. Example introduction

According to the above introduction, this method will cause a lot of problems when the program needs to rely on multiple versions of the same class.

module-a For example, there are two modules and  in the project  module-b , they depend on  two versions module-c of  1.0 and  respectively 2.0 , but because   they are loaded by the same project, only one version will be loaded in the end  module-a ,  module-b not   the   two versions.AppClassLoaderJVMmodule-c

image.png

According to  Maven the first definition in the first import order, if  module-a the definition is first, the version will be loaded finally  module-c .  1.0 At this time, if  the new feature module-b is referenced  , 2.0 it will be thrown at runtime  ClassNotFoundException.

Java The same class loaded by different class loaders  JVM is two different classes, so for the above situation, we only need to customize the class loading and  load  module-a them  module-b through two different classes (here different class loading and It does not refer to different class loading categories, just need to be loaded by two different instance objects). As mentioned above, we have customized the implementation of class loading  . We  load the  class loading reference inheritance principle   through  CustomClassLoader its two instances  loader1 and  loader2 respectively  .module-amodule-bmodule-c

image.png

It should be noted that here  CustomClassLoader1 and  CustomClassLoader2 the corresponding custom class loader must rewrite  loadClass() the method and destroy its parental delegation mechanism, otherwise it will still be proxied layer by layer, and the loader that finally loads the class will be the system class loader.

Guess you like

Origin blog.csdn.net/BASK2312/article/details/132318936