[In-depth understanding of JVM] 1. How to load in JVM class loading? Parents delegate loading class and mixed mode, code demonstration [required for interview]

 

JVM loading order

 The javac compiler-->bytecode-->class loader-->memory. The class loading step requires three steps:

1. loading (loading)

  1. Obtain the binary byte stream that defines this class through the fully qualified name of a class.
  2. Convert the static storage structure represented by this byte stream into the runtime data structure of the method area.
  3. A java.lang.Class object representing this class is generated in the java heap as the access entry for these data in the method area.
  4. After the loading phase is completed, the binary byte stream is stored in the square area according to the format required by the virtual machine.

2. linking (association)

  1. Verification (check): used to check whether the loaded class meets the parsing standard
    1. File format verification: Verify that the byte stream conforms to the Class file format specification and can be processed by the current version of the virtual machine.
    2. Metadata verification: Semantic analysis of the information described by the bytecode to ensure that the information described meets the requirements of the Java language specification
    3. Bytecode verification: This stage is mainly to analyze the data flow and control flow to ensure that the methods of the verified class will not perform behaviors that endanger the security of the virtual machine during runtime.
    4. Symbol reference verification: This phase occurs when the virtual machine converts the symbol reference into a direct reference (the parsing phase), and it mainly checks the matching of information other than the class itself. The purpose is to ensure that the parsing action can be executed normally.
  2. preparation (preparation): Assign default values ​​to static variables of the class. Generally, the basic type is 0 and the reference type is null.
    1. Formally allocate memory for variables and set initial values. These memories will be allocated in the method area. The variables here only include class scalars and do not include instance variables.
  3. Resolution: Convert the address symbols in the constant pool of the class into direct memory addresses and accessible memory addresses (there is no real initialization assignment, many people are used to confusion here)
    1. Symbol reference: A symbol reference describes the referenced target with a set of symbols. The symbol can be any form of literal, as long as the target can be located unambiguously when used. The symbolic reference has nothing to do with the memory layout implemented by the virtual machine, and the referenced target is not necessarily already loaded into the memory.
    2. Direct reference: A direct reference can be a pointer that directly points to the target, a relative offset, or a handle that can indirectly locate the target. Direct drinking is related to the memory layout.
    3. Class or interface resolution
    4. Field analysis
    5. Class method analysis
    6. Interface method analysis

3. Initalizing (initialization): The process of calling the class constructor <clinit> method, assigning initial values ​​to static member variables, and then the real initialization assignment begins . (The instruction rearrangement between 2.2 and 2.2 is also the reason for semi-instancing. You can use the plugin BinEd-Binary plugin to view the compiled bytecode file. This is also the main reason for lazy loading in singleton mode to use volatile )

Class loader

1. The Bootstrap class loader (startup class loader) is the top-level classloader. The loading path is the jar file/charset.jar and other core classes in the < JAVA_HOME >\lib directory, implemented in C++.
2. The Extension class loader (extension class loader) is responsible for loading the jar files in the < JAVA_HOME >\lib\ext directory or specified by -Djava.ext.dirs.
3. App class loader (system class loader ) is responsible for loading the JAR class package and class path specified by the classpath or java.class.path system property or CLASSPATH operating system property in the command java. If there is no self in the application Define your own class loader, under normal circumstances this is the default class loader in the program.
4. Custom is (custom class loader) , which is implemented by the user.

Diagram of the process of parental delegation

Parent delegation mechanism.

Insert picture description here

We first use code to demonstrate the existence of parental delegation. (You can verify by yourself)

Add a question here:

How is parent specified?

The source code is specified with super(parent).

Also, if not specified, the default is AppClassLoader@18b4aac2. You can try it with the code yourself. I did not find the default value for the source code.

public class T004_ParentAndChild {
    public static void main(String[] args) {
        System.out.println(T004_ParentAndChild.class.getClassLoader());
        System.out.println(T004_ParentAndChild.class.getClassLoader().getClass().getClassLoader());
        System.out.println(T004_ParentAndChild.class.getClassLoader().getParent());
        System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent());
        //System.out.println(T004_ParentAndChild.class.getClassLoader().getParent().getParent().getParent());

    }
}

打印出来是这样:
sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@12bb4df8
null

(ClassLoader loading process uses the template method pattern design pattern)

JVM is dynamically loaded on demand, using a parent delegation mechanism , from the bottom up to check whether the class is loaded (the order of 1>2>3 in the figure), if no classloader has loaded the class, it will be top-down (4>5) >6) Look for the loaded class again, until the class is found and loaded into the memory.

The advantage of using the parent delegation mechanism is for safety , to avoid conflicts between externally loaded classes and internal classloader chains, and malicious damage. If a class with the same name is loaded, the JVM will first determine whether such a class has been loaded. If it has already been loaded, the class will not be loaded again.

You can look at the source code of ClassLoader: The
name parameter is the name of the class, and the actual call is loadClass(String name)

 public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

调用loadClass(String name, boolean resolve)

The execution sequence is as shown in the figure above:

  1. First call findLoadedClass(name) to see if the same class has been loaded before. As for where to find it? I heard that first go to the hashset table in the memory to find (I didn't find the source code, you can talk about it if you find it).
  2. If you can't find it, call parent.loadClass(); here is an iteration. Until found.
  3. If it can't be found, it will call findClass(name); method throws ClassNotFoundException(name).
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
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

Why does the JVM use parental delegation?

  1. For safety (most important)
    1. If you don’t need parental delegation, load the custom loader into java.lang.String and then package it and send it to the customer, it will overwrite the built-in String library. Generally, we save passwords in String storage. At this time, if I add a business line of sending mailboxes or storing to my object's database in the custom String class, is it equivalent to using my custom String, I will easily get the password.
  2. For efficiency

Expansion:

1. Class loader encryption

It can be encrypted when the class loader is loaded, the code is as follows:

The one used here may also be encrypted.

public class T007_MSBClassLoaderWithEncription extends ClassLoader {

    public static int seed = 0B10110110;

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File f = new File("c:/test/", name.replace('.', '/').concat(".msbclass"));

        try {
            FileInputStream fis = new FileInputStream(f);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = 0;

            while ((b=fis.read()) !=0) {
                baos.write(b ^ seed);
            }

            byte[] bytes = baos.toByteArray();
            baos.close();
            fis.close();//可以写的更加严谨

            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.findClass(name); //throws ClassNotFoundException
    }

    public static void main(String[] args) throws Exception {

        encFile("com.mashibing.jvm.hello");

        ClassLoader l = new T007_MSBClassLoaderWithEncription();
        Class clazz = l.loadClass("com.mashibing.jvm.Hello");
        Hello h = (Hello)clazz.newInstance();
        h.m();

        System.out.println(l.getClass().getClassLoader());
        System.out.println(l.getParent());
    }

    /**
     * 用亦或加密
     * @param name
     * @throws Exception
     */
    private static void encFile(String name) throws Exception {
        File f = new File("c:/test/", name.replace('.', '/').concat(".class"));
        FileInputStream fis = new FileInputStream(f);
        FileOutputStream fos = new FileOutputStream(new File("c:/test/", name.replaceAll(".", "/").concat(".msbclass")));
        int b = 0;

        while((b = fis.read()) != -1) {
            // 亦或一个数,再亦或的话就解密了
            fos.write(b ^ seed);
        }

        fis.close();
        fos.close();
    }
}

2. When does the class loader start to initialize?

The JVM specification does not specify when to load. But it strictly stipulates when it must be initialized.

Class loading is not to load all classes during initialization, but to load classes when they are used. Lazy loading will happen. There are five situations for lazy loading:

  1. New object, get and access static variables, remember to access final variables except.

  2. When java.lang.reflect makes a reflection call to a class.

  3. When initializing a subclass, the parent class is initialized first.

  4. When the virtual machine starts, the main class to be executed must be initialized.

  5. When the dynamic language support java.lang.invoke.MethodHandle resolves to the method handle of REF_getstatic REF_putstatic REF_invokestatic, the class must be initialized.

3. How to break the parental delegation mechanism?

We can infer from the above source code that as long as the loadClass() method of the ClassLoader is overridden, the loadClass() of the parent class is not called in the loadClass method, but directly to load your own class, so that the parent delegation mechanism can be broken .

4. When will the parental delegation mechanism be broken?

  1. Before the JDK1.2 version, custom custom ClassLoader must override loadClass().
  2. ThreadContextClassLoader can implement the basic class call implementation class code, which is specified by thread.setContextClassLoader.
  3. Hot Start. osgi tomcat has its own module designated classloader (different versions of the same class library can be loaded)
    1. The hot deployment of tomcate breaks the parent delegation mechanism. Modifying a class file can be synchronized to the context immediately. In fact, the essence is to reload the class once. If you are interested, you can learn about the implementation principle of tomcat. Wrote the loadClass() method.

Rewrite the loadClass() code:

public class T012_ClassReloading2 {
    private static class MyLoader extends ClassLoader {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {

            File f = new File("C:/work/ijprojects/JVM/out/production/JVM/" + name.replace(".", "/").concat(".class"));

            if(!f.exists()) return super.loadClass(name);

            try {

                InputStream is = new FileInputStream(f);

                byte[] b = new byte[is.available()];
                is.read(b);
                return defineClass(name, b, 0, b.length);
            } catch (IOException e) {
                e.printStackTrace();
            }

            return super.loadClass(name);
        }
    }

    public static void main(String[] args) throws Exception {
        MyLoader m = new MyLoader();
        Class clazz = m.loadClass("com.mashibing.jvm.Hello");

        m = new MyLoader();
        Class clazzNew = m.loadClass("com.mashibing.jvm.Hello");

        System.out.println(clazz == clazzNew);
    }
}

5. Mixed mode of JVM

 

By default, it is a mixed mode (mixed interpreter + hot code compilation).

Java is interpreted and executed. After the class file is in the memory, it is executed through the Java interpreter-bytecode intepreter.

JIT (Just In-Time Compiler): Some codes will be compiled into local format code for execution.

Therefore, Java cannot be simply said to be an interpreted language or a compiled language.

1. When will JIT be used to compile local code?

    I wrote a piece of code, and it was executed with an interpreter at the beginning. It turned out that during the execution process, a certain piece of code was executed very frequently (executed hundreds of thousands of times in 1s), and the JVM would compile this piece of code into local code ( Similar to using C language to compile the local *.exe file), when the code is executed again, the interpreter will not be used to execute it, which improves efficiency.

2. Why not directly compile local code to improve execution efficiency?

  1. The execution efficiency of the java interpreter is actually very high, and the execution efficiency of some codes is not necessarily lost to the execution of native code.
  2. If the executed code references a lot of class libraries, the execution time will be very long.

Three, use parameters to change the mode:

  1. -Xmixed: The default is mixed mode, the startup speed is faster, and the hot code is detected and compiled.
  2. -Xint: Use interpretation mode, start up quickly, and execute slightly slower.
  3. -Xcomp: Use pure compilation mode, fast execution, slow startup (when there are many libraries)

Code verification:

Edit Configurations-->VM options

  • Mixed mode:
    • Do not modify any parameters and use the default configuration
    • Execution time: about 2700
  • Explanation mode:
    • 将Edit Configurations-->VM options-->-Xint
    • Execution time: the execution time is too long, minus one cycle of 0,19000
  • Compilation mode:
    • 将Edit Configurations-->VM options-->-Xcomp
    • Execution time: 2600
public class T009_WayToRun {
    public static void main(String[] args) {
        for(int i=0; i<10_0000; i++)
            m();

        long start = System.currentTimeMillis();
        for(int i=0; i<10_0000; i++) {
            m();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }

    public static void m() {
        for(long i=0; i<10_0000L; i++) {
            long j = i%3;
        }
    }
}

Next article: [In-depth understanding of JVM] 3. CPU storage + MESI + CPU pseudo-sharing + CPU disorder problem and code demonstration [interview essential]

Guess you like

Origin blog.csdn.net/zw764987243/article/details/109502435