Talk about JVM class loading

Keywords: Programming jvm Java github

ClassLoader: A class loader responsible for loading a Class into the JVM and resolving the Class byte code into an object format that is universally required by the JVM.The entire life cycle consists of seven phases: loading, validating, preparing, parsing, initializing, using, and unloading.

1. How to load the class file

1.1, Loading

  1. Gets the binary byte stream describing a class by its fully qualified name;
  2. Save the static storage structure represented by this byte stream as the runtime data structure of the method area;
  3. Generate a java.lang.Class object representing this class in the Java heap as an entry to the method area;

1.2, Validation

To ensure that the Class file meets the current virtual machine requirements, the following validations are required:

  1. Format validation: Verify that byte streams conform to the specifications of the class file format and can be processed by the current virtual machine, such as whether they start with magic 0xCAFEBABE, whether the major and minor version numbers are within the current virtual machine processing range, whether constant pools have unsupported constant types, and so on.Only format-validated byte streams are stored in the data structure of the method area, and the remaining three validations are based on the data of the method area.
  2. Metadata validation: Semantic analysis of data described by byte codes to ensure compliance with Java language specifications, such as whether final-modified classes are inherited, whether abstract methods of parent classes are implemented, whether final methods or final fields of parent classes are overwritten, and so on.
  3. Byte code validation: The method body of the class is analyzed to ensure that there are no events harmful to the virtual machine when the method runs, such as ensuring the matching of data type and instruction code sequence of the operand stack, ensuring the correctness of jump instructions, and ensuring the validity of type conversion.
  4. Symbol reference validation: To ensure that subsequent parsing can be performed properly, the symbol reference is validated, such as whether the fully qualified name described by the string can find the corresponding class, whether there is a method-compliant field descriptor in the specified class, and so on.

1.3. Preparation

In the preparatory phase, allocate memory in the method area for class variables (static modifiers) and set an initial value.

If the boolean value is false by default, if the object reference is null by default, and so on...

private static int var = 100;

When the preparation phase is complete, the var value is 0, not 100.In the initialization phase, 100 is assigned to val, but there is a special case:

private static final int VAL= 100;

The ConstantValue property is generated for the VAL during compilation, and the virtual opportunity assigns the VAL a value of 100 based on the ConstantValue property during preparation.

Note: Only static variables in the class (method area) are set, not instance variables (in heap memory). Instance variables initialize the allocated values when an object is instantiated

1.4, Resolution

The parsing phase is when a virtual machine replaces a symbolic reference in a constant pool with a direct reference.

  1. Symbol reference: A simple understanding is that a string, such as a reference to a class, java.util.ArrayList This is a symbol reference, and the objects referenced by a string are not necessarily loaded.
  2. Direct reference: Pointer or address offset.Reference objects must be in memory (loaded).

1.5, Initialization

  1. Execute class constructor <clinit>
  2. Initialize static variables, data in static blocks, etc. (A class loader only initializes once)
  3. Subclass <clinit>Ensure parent <clinit>is called before call

Note: <clinit>is thread-safe. Threads executing <clinit>need to acquire locks before they can be initialized, ensuring that only one thread can execute <clinit> (a lazy singleton mode that allows thread security).

2. Class Initialization Scenario

There are only five cases where classes must be initialized in a virtual machine.

  1. Execute new, getstatic, putstatic, and invokestatic instructions;
  2. Use reflect to make a reflection call to the class;
  3. When a class is initialized, the parent class has not been initialized, and the parent class is initialized beforehand.
  4. When you start a virtual machine, you need to initialize the class that contains the main method;
  5. In JDK1.7, if the final parsing result of the java.lang.invoke.MethodHandler instance is a method handle of REF_getStatic, REF_putStatic, REF_invokeStatic, and the corresponding class of the method handle is not initialized;

Class initialization will not be triggered in the following cases

1. Referencing a static field of a parent class through a subclass only triggers the initialization of the parent class, not the initialization of the subclass.

class Parent {
    static int a = 100;
    static {
        System.out.println("parent init!");
    }
}

class Child extends Parent {
    static {
        System.out.println("child init!");
    }
}

public class Init{  
    public static void main(String[] args){  
        System.out.println(Child.a);  
    }  
}

The output is:

parent init!
100

2. Define an array of objects that will not trigger the initialization of the class.

public class Init{  
    public static void main(String[] args){  
        Parent[] parents = new Parent[10];
    }  
}

No output, indicating that the initialization of the class Parent was not triggered, but what did this code do?First look at the generated byte code instructions

The anewarray directive allocates space for the new array and triggers the initialization of the [Lcom.ctrip.ttd.whywhy.Parent class, which is automatically generated by the virtual machine.

3. Constants are stored in the constant pool of the calling class during compilation. Classes that define constants are not directly referenced in nature and do not trigger the class in which they are defined.

class Const {
    static final int A = 100;
    static {
        System.out.println("Const init");
    }
}

public class Init{  
    public static void main(String[] args){  
        System.out.println(Const.A);  
    }  
}

Output: 100

Explanation does not trigger initialization of class Const. During compilation, the value 100 of constant A in the Const class is stored in the constant pool of the Init class, and the two classes are unrelated after compiling into a class file.

4. Getting a Class object by its class name will not trigger the initialization of the class.

public class test {
   public static void main(String[] args) throws ClassNotFoundException {
        Class c_dog = Dog.class;
        Class clazz = Class.forName("zzzzzz.Cat");
    }
}

class Cat {
    private String name;
    private int age;
    static {
        System.out.println("Cat is load");
    }
}

class Dog {
    private String name;
    private int age;
    static {
        System.out.println("Dog is load");
    }
}

Execution result: Cat is load, so initialization of the Dog class does not trigger through Dog.class.

5. When loading a specified class through Class.forName, class initialization will not be triggered if the specified parameter initialize is false. In fact, this parameter tells the virtual machine whether to initialize the class or not.

public class test {
   public static void main(String[] args) throws ClassNotFoundException {
        Class clazz = Class.forName("zzzzzz.Cat", false, Cat.class.getClassLoader());
    }
}
class Cat {
    private String name;
    private int age;
    static {
        System.out.println("Cat is load");
    }
}

6. Initialization will not be triggered by the ClassLoader default loadClass method

new ClassLoader(){}.loadClass("zzzzzz.Cat");

3. Class Loader in JVM and Parental Delegation Mechanism

The entire JVM platform provides three layers of ClassLoader.

  1. BootstrapClassLoader, which mainly loads the classes needed by the JVM for its own work. This ClassLoader is entirely controlled by the JVM itself. Which class to load and how to load are controlled by the JVM itself. No one else can access this class. Therefore, this ClassLoader does not obey the loading rules described earlier. It is just a loading tool for classes, and there is no higher-level parent loading.And there are no subloaders.
  2. ExtClassLoader, this class loader is a bit special, it is part of the JVM itself, but its lineage is not very pure, it is not achieved by the JVM itself, he loads the target under the System.getProperty("java.ext.dirs") directory.
  3. AppClassLoader, whose parent is ExtClassLoader.It loads into the System.getProperty("java.class.path") directory, which is the class path that we often use.

Here, it is important to note that JVM loads classes with a parental delegation mechanism by default.Generally speaking, when a specific class loader receives a request to load a class, it delegates the loading task to the parent loader first, and then recursively, if the parent loader can complete the class loading task, it will return successfully; only if the parent loader cannot complete the loading task, it will load itself.

The following is a diagram of the relationship between a standard class loader:

Both ExtClassLoader and AppClassLoader are in the sun.misc.Launcher class, and there isn't much code. Let's take a look.

public class Launcher {  
    private static URLStreamHandlerFactory factory = new Factory();  
    private static Launcher launcher = new Launcher();  

    public static Launcher getLauncher() {  
        return launcher;  
    }  

    private ClassLoader loader;  

    //ClassLoader.getSystemClassLoader calls this method  
    public ClassLoader getClassLoader() {  
        return loader;  
    }  

    public Launcher() {  
        // 1. Create ExtClassLoader   
        ClassLoader extcl;  
        try {  
            extcl = ExtClassLoader.getExtClassLoader();  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create extension class loader");  
        }  

        // 2. Use ExtClassLoader as parent to create AppClassLoader   
        try {  
            loader = AppClassLoader.getAppClassLoader(extcl);  
        } catch (IOException e) {  
            throw new InternalError(  
                "Could not create application class loader");  
        }  

        // 3. Set AppClassLoader to ContextClassLoader  
        Thread.currentThread().setContextClassLoader(loader);  
        //...  
    }  

    static class ExtClassLoader extends URLClassLoader {  
        private File[] dirs;  

        public static ExtClassLoader getExtClassLoader() throws IOException  
        {  
            final File[] dirs = getExtDirs();  
            return new ExtClassLoader(dirs);  
        }  

        public ExtClassLoader(File[] dirs) throws IOException {  
            super(getExtURLs(dirs), null, factory);  
            this.dirs = dirs;  
        }  

        private static File[] getExtDirs() {  
            String s = System.getProperty("java.ext.dirs");  
            File[] dirs;  
            //...  
            return dirs;  
        }  
    }  

    /** 
     * The class loader used for loading from java.class.path. 
     * runs in a restricted security context. 
     */  
    static class AppClassLoader extends URLClassLoader {  

        public static ClassLoader getAppClassLoader(final ClassLoader extcl)  
            throws IOException  
        {  
            final String s = System.getProperty("java.class.path");  
            final File[] path = (s == null) ? new File[0] : getClassPath(s);  

            URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);  
            return new AppClassLoader(urls, extcl);  
        }  

        AppClassLoader(URL[] urls, ClassLoader parent) {  
            super(urls, parent, factory);  
        }  

        /** 
         * Override loadClass so we can checkPackageAccess. 
         * This seems unnecessary because super.loadClass(name, resolve) also checkPackageAccess 
         */  
        public synchronized Class loadClass(String name, boolean resolve)  
            throws ClassNotFoundException  
        {  
            int i = name.lastIndexOf('.');  
            if (i != -1) {  
                SecurityManager sm = System.getSecurityManager();  
                if (sm != null) {  
                    //  
                    sm.checkPackageAccess(name.substring(0, i));  
                }  
            }  
            return (super.loadClass(name, resolve));  
        }  

    }  
}

Launcher in the code is constructed as the main entry, and it is easy to see that JVM sets AppClassLoader's parent class loader to ExtClassLoader, while ExtClassLoader has no parent class loader.In fact, many articles listed Bootstrap ClassLoader in the upper level of ExtClassLoader when introducing the hierarchy of ClassLoader. In fact, Bootstrap ClassLoader does not belong to the class hierarchy level of JVM because Bootstrap ClassLoader does not obey the loading rules of ClassLoader.Bootstrap ClassLoader also has no subclasses, and the top-level parent we can extract from the application is ExtClassLoader.

If we want to implement our own class loader, whether you implement the abstract class ClassLoader directly, inherit the URLClassLoader class, or other subclasses, its parent loader is AppClassLoader, because whatever parent constructor you call, the object you create must ultimately call getSystemClassLoader() as the parent loader.The getSystemClassLoader () method gets AppClassLoader.

The loading process for this class is as follows:

Let's implement a class loader of our own.

public class PathClassLoader extends ClassLoader {
    private String classPath;

    public PathClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classFileName = getFileName(name);
        File file = new File(classPath, classFileName);

        try {
            FileInputStream is = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            int len = 0;
            while ((len = is.read()) != -1) {
                bos.write(len);
            }
            byte[] data = bos.toByteArray();
            is.close();
            bos.close();

            if (data == null) {
                throw new ClassNotFoundException();
            }
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    //Get the class file name to load
    private String getFileName(String name) {
        int index = name.lastIndexOf('.');
        if(index == -1){
            return name+".class";
        }else{
            return name.substring(index + 1) + ".class";
        }
    }

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, UnsupportedEncodingException {
        PathClassLoader classLoader = new PathClassLoader("/Users/Benjamin/Desktop");
        Class<?> aClass = classLoader.loadClass("com.github.classloader.Test");
        Method method = aClass.getDeclaredMethod("say");
        method.invoke(aClass.newInstance());
    }
}

The above is a simple implementation of loading a local class file and calling the say method.We can also use custom class loading to perform other functions, perhaps in the following ways:

  1. Load the class file under the custom path.
  2. Load a class file in a custom format.
  3. Implement hot deployment of classes.
  4. class file encryption and decryption transfer.

Reproduced from: Talk about JVM class loading

Posted by willsavin on Tue, 14 Apr 2020 20:21:47 -0700