Class loading

  1. Loading is the first stage of the whole class loading. In the loading stage, the jvm obtains the binary byte stream defining this class through the fully qualified name of the class. The linking work can start before the loading stage is completely completed java doc of ClassLoader
  2. The three stages of linking verification, preparation and parsing are called connection
    1. Verification
    2. Preparation
    3. Resolution
  3. Initialization

The JVM SPEC does not specify when to execute Loading, but for Initialization, it clearly stipulates that there are and only the following six situations in which classes must be initialized immediately (of course, Loading and Linking may have been completed, or Loading and Linking may be executed first) This is called active use of classes

  1. When encountering new, getstatic, putstatic, or invokestatic, if the type is not initialized, it needs to be initialized first Typical java code scenarios are:
    • Read or set a static field of a type (except in the case of explicitly putting the result into the current class constant pool at compile time)
    • When calling a static method of a class
    • When using the new keyword to create a type instance
  2. When reflecting on a type, if the class has not been initialized
  3. When initializing a class, if it is found that its parent class has not been initialized, it will recursively initialize the parent class until the top level (except for the interface, which will be proved below)
  4. When the virtual machine starts, the class containing the main method will be initialized
  5. When using the dynamic language support newly added in JDK7, if the final parsing result of a MethodHandler is REF_getstatic, REF_putstatic, REF_invokestatic, REF_invokespecial four types of method handles, and the corresponding type of this handle is not initialized
  6. If a default method is defined in an interface, the implementation class of this interface will be initialized

Here are some typical passive uses of classes

  1. Using the static field of the parent class through the subclass will not lead to the initialization of the subclass, but will load the subclass
  2. Referencing a class through an array definition will not initialize the array member class, but will initialize a class of "[Lxxx.xxx.Clazz". A class that directly inherits the Object automatically generated by the virtual machine triggered by newarray. This class wraps the access of array elements
  3. The compilation stage can be directly put into the constant pool
  4. ClassLoader.loadClass also does not cause class initialization

The JVM comes with the following types of Loaders

  • Root / boot class loader: it has no parent loader Responsible for loading the core class library of virtual machine Including stored in Java_ The home / lib directory, or the class library with the specified name (such as rt.jar, tool.jar, etc.) stored in the path specified by the - X:bootclasspath parameter, will not be loaded if the name does not match
  • Extension class loader: responsible for loading Java_ In the home / lib / ext directory or by Java All class libraries in the path specified by ext.dirs system variable Because of its particularity, this class loader will only load the classes in the jar package. If you only put the custom class file in the loading path, it will not be loaded. If you use jar cvf... To form a jar package, you can load it (jdk8)
  • Application class loader: it is responsible for loading all class libraries on the classpath

Execute the following code in jdk8 environment to output the loading path of each class loader. jdk15 has been empty, which may be due to the modularity introduced by jdk9, which leads to a new way to load classes

public class Loader {
    public static void main(String[] args) {
        System.out.println(System.getProperty("sun.boot.class.path"));
        System.out.println(System.getProperty("java.ext.dirs"));
        System.out.println(System.getProperty("java.class.path"));
    }
}

Parents delegation model of class loader

The parent delegation model requires that all Class loaders except the top-level startup Class loader should have their own parent loader. If a Class loader receives a request to load a Class, it will not load the Class itself first, but delegate the request to the parent loader to complete it. This will be the case for Class loaders of each layer, The current Class loader will attempt to load the specified Class only if the parent Class cannot be loaded If a Class loader can successfully load a Class, the Class loader is called the definition Class loader of the Class, and all Class loaders (including the definition Class loader) that can successfully return the Class object reference are called the initial Class loader The following is the loadClass source code of ClassLoader Class. You can see that the implementation of this model is very simple

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
                    PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

Significance of parental delegation model

  • It can ensure the type safety of the java core library: all java applications will at least reference the Object class, which will be loaded into the virtual machine during runtime If this loading process can be completed by the application custom loader, it is likely that there will be Object classes belonging to multiple namespaces in the JVM, and these classes are incompatible With the help of the parent delegation model, we can ensure that the core classes are loaded by the same loader
  • It can ensure that java core classes will not be replaced by custom classes

What's the point of breaking the parental delegation mechanism

Different class loaders can create additional namespaces for classes with the same binary name Classes with the same name can be stored in the virtual machine. You only need to load them with different class loaders The types loaded by different class loaders are incompatible, which is equivalent to creating many isolated java class spaces in the JVM. This kind of technology has been actually used in many mines, such as Pandora OSGI

Thread context classloader

From java1 2. This class loader can be set through Thread#setContestClassLoader If it is not set when creating a thread, it will inherit one from the parent thread. If it is not set in the global scope of the application, it is the system class loader by default In order to solve the problem that the boot class loader cannot load the provider class of SPI In this way, the parent ClassLoader can use the class loaded by the thread context class loader of the current thread, which changes the situation that the parent ClassLoader cannot use the class loaded by the child ClassLoader or other classloaders without direct parent-child relationship, and breaks the parental delegation model

SPI is a typical use of Thread Context ClassLoader to break the parental delegation model to load the required SPI service code. This is a behavior that the parent class loader requests the child class loader to complete class loading

Namespace of class loader

  • Each class loader has its own namespace, which is composed of the classes loaded by the loader and all its parent loaders
  • Classes in the same namespace are mutually visible The namespace of the child loader contains the namespaces of all the parent loaders, so the classes loaded by the child loader can see all the classes loaded by the parent loader, and vice versa If two loaders have no direct or indirect parent-child relationship, the classes they load are not visible to each other
  • In the same namespace, two classes with the same complete name (including package name) of the class will not appear; In different namespaces, there may be

Class uninstallation

When the class loader and all instances of a class are no longer alive, the class will be unloaded during gc. Pay attention! Only the classes loaded by the user-defined class loader can be unloaded

Details of class loading

  1. Class loading does not need to wait until a class is "actively used for the first time". The JVM can choose whether to load a class in advance without performing operations such as connection and initialization The JVM specification allows class loaders to preload a class when they expect it to be used, if they encounter problems during preloading If the class file is missing or there is an error, the class loader must report the error when the program actively uses the class for the first time. If this class has not been actively used by the program, the class loader will not report the error during the operation of the program??? There is no clear proof here

  2. For static fields, only the class that directly defines the field will be initialized, but the loader may load the relevant classes at the same time (passive reference case 1)

    public class PreLoad {
        public static void main(String[] args) {
            int c = PreLoadSub.a;
        }
    }
    
    class PreLoadParent {
        public static int a = 1;
        static {
            System.out.println("Parent: <cinit>");
        }
    }
    
    class PreLoadSub extends PreLoadParent {
        public static int b = 2;
        static {
            System.out.println("Sub: <cinit>");
        }
    }
    

    The above code will only output parent: < cinit > if you use - XX:+TraceClassLoading to start TraceClassLoading, you will see the process of loading the parent class first and then the child class

    [0.150s][info ][class,load] org.wm.loading.PreLoadParent
    ...
    [0.150s][info ][class,load] org.wm.loading.PreLoadSub

  3. When a JVM initializes a class, all its parent classes are required to be initialized, but this rule does not apply to interfaces

    • When initializing a class, the interface it implements is not initialized first

    • When initializing an interface, its parent interface is not initialized first

      public class LoadingInterface {
          public static void main(String[] args) {
              new ImplClass();    // Running this code alone will output implclass < cinit >
              Thread sentinel = Child.sentinel;   // Running this code alone will output child interface < cinit >
          }
      }
      
      interface Parent {
          int a = 1;
          Thread sentinel = new Thread() {
              {
                  System.out.println("Parent interface <cinit>");
              }
          };
      }
      
      interface Child {
          int b = 2;
          Thread sentinel = new Thread() {
              {
                  System.out.println("Child interface <cinit>");
              }
          };
      }
      
      class ImplClass implements Parent {
          static {
              System.out.println("ImplClass <cinit>");
          }
      }
      
    • The exception is the sixth case of class initialization. If the default method is defined in the interface, the interface needs to be initialized before implementing the class

      package org.wm.loading;
      
      public class LoadingInterface {
          public static void main(String[] args) {
              new ImplClass();    
            	// Running this code will output child interface < cinit >
      				// ImplClass <cinit>
          }
      }
      
      
      interface Child {
          int b = 2;
          Thread sentinel = new Thread() {
              {
                  System.out.println("Child interface <cinit>");
              }
          };
      
          default void defaultMethod() {
      
          }
      }
      
      class ImplClass implements Child {
          static {
              System.out.println("ImplClass <cinit>");
          }
      
          @Override
          public void defaultMethod() {
      
          }
      }
      
      

    Therefore, a parent interface is not initialized because of the initialization of its child interface or its implementation class Only when the program uses the interface for the first time (for example, when using non compile time constants defined in the interface), will it cause the initialization of the interface

  4. Each class will try to use the class loader that loads it to load the class it depends on, which can lead to the thread context class loader

Keywords: Java Back-end

Added by tensionx on Sat, 05 Feb 2022 20:11:11 +0200