JVM Part 14 (class loading and bytecode Technology V)

Class loading phase

The class loading stage is divided into three stages: loading, linking and initialization.

load

Load the bytecode of the class into the method area, and use instanceKlass of C + + to describe the java class.
Its important field s are:
_ java_mirror, that is, the class image of Java. For example, for String, it is String.class.
_ super is the parent class
_ fields are member variables
_ Methods are methods
_ constants is the constant pool
_ class_loader is the class loader
_ vtable virtual method table
_ Table of itable interface methods

If this class has a parent class that has not been loaded, load the parent class first
Loading and linking may run alternately

Metadata like instanceKlass is stored in the method area (in the meta space after JDK1.8), but_ java_mirror
Is stored in the heap.

Store instanceKlass in the meta space_ java_ The mirror is stored in the heap and instanceKlass_ java_ A reference to the mirror, in the heap_ java_ The mirror stores the instanceKlass reference. There are two Person objects in the heap. The Person object finds instanceKlass through Person.class to obtain class information.

link

Verify whether the class conforms to the JVM specification and check the security
Preparation stage
Allocate space for the static variable and set the default value.
The static variable is stored at the end of instanceKlass before JDK 7. Starting from JDK 7, it is stored in_ java_ End of mirror.
As shown in the figure, the static variable is stored in the_ java_ End of mirror.

static variable space allocation and assignment are two steps. Space allocation is completed in the preparation stage and assignment is completed in the initialization stage.
static variable is the basic type of final and string constant. Then the value is determined in the compilation stage and the assignment is completed in the preparation stage.
If the static variable is final but is of reference type, the assignment will also be completed in the initialization phase
analysis
Resolves a symbolic reference in a constant pool to a direct reference
Code example

package cn.itcast.jvm.t3.load;
/**
* Meaning of analysis
*/
public class Load2 {
	public static void main(String[] args) throws ClassNotFoundException,IOException {
		ClassLoader classloader = Load2.class.getClassLoader();
		// The loadClass method does not cause class resolution and initialization
		Class<?> c = classloader.loadClass("cn.itcast.jvm.t3.load.C");
		// new C(); If an object is used, it will be parsed and initialized.
		System.in.read();
	}
} 
class C {
	D d = new D();
} 
class D {
}

Class loading is lazy. Unused classes will not be loaded, parsed and initialized.
In the program, class C is loaded, but class C is not used. Class C will not be parsed and initialized in the loading of class C. class D is used in class C, and class D will not be loaded, parsed and initialized. At this point, in the constant pool of class C, class D is just a symbolic reference

If new C() in the program is used; In addition, class C will be parsed and initialized. Because class D is new in class C, and class D is parsed and initialized at the same time, the symbolic reference to D in class C becomes a direct reference.

initialization

cinit()V method
Initialization calls () V, and the virtual opportunity ensures the thread safety of the "constructor" of this class.
Class initialization is lazy
Initialization occurs:

  1. The class of the main method is always initialized first
  2. The first time you access a static variable or static method of this class
  3. Subclass initialization. If the parent class has not been initialized, an
  4. When a subclass accesses the static variables of the parent class, it will only trigger the initialization of the parent class
  5. Class.forName
  6. new causes initialization

Conditions that do not cause class initialization:

  1. static final static constants (basic types and strings) that access classes do not trigger initialization
  2. class object. class does not trigger initialization
  3. Creating an array of this class does not trigger initialization
  4. loadClass method of class loader
  5. When parameter 2 of Class.forName is false

Class loader

Hierarchical relationship of class loader (taking JDK8 as an example)

Bootstrap boot class loader, Extension class loader, Application class loader. Bootstrap class loader cannot be accessed directly because bootstrap is written in C + + code and does not support direct access by java code.

Bootstrap boot class loader

Load classes with the Bootstrap class loader:

package cn.itcast.jvm.t3.load;
public class F {
	static {
		System.out.println("bootstrap F init");
	}
}
package cn.itcast.jvm.t3.load;
public class Load5_1 {
	public static void main(String[] args) throws ClassNotFoundException {
		Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.F");
		System.out.println(aClass.getClassLoader());
	}
}

If you execute the program directly in the compiler, you can see that the default loader of class F is ApplicationClassLoader.
Add the - Xbootclasspath parameter in the command line to add the path of the current class to the startup path, and class F will be loaded by Bootstrap.

java -Xbootclasspath/a:. cn.itcast.jvm.t3.load.Load5
bootstrap F init
null

In the program, the output class loader is null because the Bootstrap class loader cannot be accessed directly by java code.

extensions class loader

Extension ClassLoader loads Java using the Extension ClassLoader_ The class under the path home / JRE / lib / ext.

package cn.itcast.jvm.t3.load;
public class G {
	static {
		System.out.println("ext G init");
	}
}
public class Load5_2 {
	public static void main(String[] args) throws ClassNotFoundException {
		Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.G");
		System.out.println(aClass.getClassLoader());
	}
}

In the above program, if it is executed directly, the class loader that loads class G is sun.misc.Launcher A p p C l a s s L o a d e r @ 18 b 4 a a c 2 , by answer use class plus load implement . take When front of class hit by j a r package , however after discharge stay J A V A H O M E / j r e / l i b / e x t road path lower , again second Hold that 's ok Course order , be transport Out junction fruit by : s u n . m i s c . L a u n c h e r AppClassLoader@18b4aac2 , an application class loader. Type the current class as a jar package and put it in Java_ Under the path of home / JRE / lib / ext, execute the program again, and the output result is sun.misc.Launcher AppClassLoader@18b4aac2 , an application class loader. Type the current class as a jar package, put it in the path of JAVAH # OME/jre/lib/ext, and execute the program again. The output result is sun.misc.exe LauncherExtClassLoader@29453f44 , this is the extension class loader.

Parental delegation mode

The so-called parent delegation refers to the rules for finding classes when calling the loadClass method of the class loader
When loading a class, first check whether the high-level loader has loaded the class. If not, the class loader at this level will load it. If the class loader at this level does not find a class, a ClassNotFound exception will be thrown.
Source code analysis:

protected Class<?> loadClass(String name, boolean resolve)throws  ClassNotFoundException{
	synchronized (getClassLoadingLock(name)) {
	// 1. Check whether the class has been loaded and look for it in the loaded class.
	Class<?> c = findLoadedClass(name);
	if (c == null) {	
		long t0 = System.nanoTime();
		try {
			if (parent != null) {
				// 2. If there is a superior, delegate the superior loadClass
				c = parent.loadClass(name, false);
			} else {
				// 3. If there is no superior (ExtClassLoader), delegate bootstrap classloader
				c = findBootstrapClassOrNull(name);
			}
		} catch (ClassNotFoundException e) {
		} 
		if (c == null) {
			long t1 = System.nanoTime();
			// 4. If each layer cannot be found, call findClass method (each class loader extends itself) to load
			c = findClass(name);
			// 5. Recording time
			sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
			sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
			sun.misc.PerfCounter.getFindClasses().increment();
		}
	} 
	if (resolve) {
		resolveClass(c);
	} 
	return c;
	}
}

Source code summary, class loading process: custom class loader delegation - > application class loader delegation - > extended class loader - > start class loader. Startup class loader cannot find class - > extension class loader cannot find class - > application class loader cannot find class - > custom class loader cannot find - > classnotfound exception.

context class loader

The reason for the occurrence of thread up and down class loader:
The more basic classes are loaded by the higher level loader. What should I do if the basic class calls back the user's code?
Solution: use the thread context class loader

This class loader can be set through the setContextClassLoader () method of java.lang.Thread class. 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, this class loader is the application class loader by default.

With the thread context class loader, that is, the parent class loader requests the child class loader to complete the action of class loading (that is, the class loaded by the parent class loader uses the thread context loader to load the class that cannot be loaded). This behavior actually opens up the hierarchy of the parent delegation model to reverse the use of the class loader, In fact, it has violated the general principles of the parental delegation model.

SPI (Service Provider Interface - service provider interface)
All loading actions involving SPI in Java basically adopt this method

JDBC uses pseudo code:

Class.forName("com.mysql.driver.Driver");
Connection conn = Driver.getConnection();
Statement st = conn.getStatement();

JDBC is a standard. Different database vendors will have their own implementations according to this standard. The JDBC interface exists in the JDK. Therefore, these JDBC related interfaces are loaded by the startup class loader at startup.
Generally, the jar package provided by the database manufacturer is placed under the classPath. It can be seen that the implementation classes provided by the database manufacturer will not be loaded by the startup class loader, but they are usually loaded by the application class loader.
The interface is loaded by the startup class loader, and the specific implementation is loaded by the application class loader. According to the parent delegation principle of a class, the class / interface loaded by the parent loader cannot see the class / interface loaded by the child loader, but the class / interface loaded by the child loader can see the class / interface of the parent loader. This will lead to a situation: JDBC related code may need to call the code in the specific implementation class, but it cannot see the specific implementation class.

getContextClassLoader() and setContextClassLoader(ClassLoader cl) in Thread are used to obtain and set the context class loader respectively. The context class loader of the initial Thread of the Java application runtime is the application class loader. The code running in the Thread can load classes and resources through the class loader.
The parent ClassLoader can use the class loaded by the ClassLoader specified by the current Thread.currentThread().getContextClassLoader(). This changes the situation that the parent ClassLoader cannot use the child ClassLoader or other classes loaded by classloaders without direct parent-child relationship, that is, the two parent delegation model is changed.

In the parent delegation model, the class loader is bottom-up, that is, the lower class loader will delegate the upper class to load. However, for SPI, some interfaces are provided by the Java core library, which is loaded by the startup class loader, and the implementation of these interfaces comes from different jar packages. The Java startup class loader will not load jar packages from other sources, so the traditional parental delegation model can not meet the requirements of SPI. By setting the context class loader for the current thread, the set context class loader can load the interface implementation class.

The function of ContextClassLoader is to destroy Java's class loading delegation mechanism.
When the high-level provides a unified interface for the low-level to implement, and also loads (or instantiates) the low-level classes at the high-level, it is necessary to help the high-level ClassLoader find and load the classes through the thread context class loader.

Reference Author: tomas' small rattle

Observe ContextClassLoader through JDBC:
Let's trace the code and observe the ContextClassLoader
loadInitialDrivers() in the static code block in class DriverManager; Method to load the implementation class.

static {
 	loadInitialDrivers();
 	println("JDBC DriverManager initialized");
}

In the loadInitialDrivers method, use the ServiceLoader mechanism to load the driver, that is, SPI

// Load the implementation class in the load method
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) {
	driversIterator.next();
}

load method

public static <S> ServiceLoader<S> load(Class<S> service) {
	// Gets the thread context class loader, which defaults to the application class loader
	ClassLoader cl = Thread.currentThread().getContextClassLoader();
	return ServiceLoader.load(service, cl);
}

Thread context class loader is the class loader used by the current thread. By default, it is the application class loader, which is internally controlled by
Class.forName calls the thread context class loader to complete class loading. The specific code is in the internal class of ServiceLoader
nextService method in lazyitterer:

// loader is cl 
c = Class.forName(cn, false, loader);

In the loadInitialDrivers method, the driver is also loaded using the driver name defined by jdbc.drivers

println("DriverManager.Initialize: loading " + aDriver);
// The class loader obtained by getSystemClassLoader is the application class loader
Class.forName(aDriver,true,ClassLoader.getSystemClassLoader());

Custom class loader

When do I need a custom class loader?

  1. Want to load a class file in a non classpath arbitrary path
  2. They are implemented through interfaces. When decoupling is desired, it is often used in framework design
  3. These classes want to be isolated. Classes with the same name in different applications can be loaded without conflict. They are common in tomcat containers

Steps:

  1. Inherit ClassLoader parent class
  2. To comply with the parental delegation mechanism, override the findClass method,
    Note that the loadClass method is not rewritten, otherwise the two parent delegation mechanism will not be used
  3. Read bytecode of class file
  4. Call the defineClass method of the parent class to load the class
  5. The user calls the loadClass method of this class loader

Sample program

Customize the class loader to load the class file. Class file in the specified directory

class MyClassLoader extends ClassLoader {
    @Override // Name is the class name
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = "e:\\myclasspath\\" + name + ".class";
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Files.copy(Paths.get(path), os);
            // Get byte array
            byte[] bytes = os.toByteArray();
            // byte[] -> *.class
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("Class file not found", e);
        }
    }
}
// Test custom class loader
public class Load7 {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> c1 = classLoader.loadClass("MapImpl1");
        Class<?> c2 = classLoader.loadClass("MapImpl1");
        System.out.println(c1 == c2);
        c1.newInstance();
    }
}

Run time optimization

When the program is running, the JVM optimizes the program according to the situation.

Layered compilation

Code example

// -XX:+PrintCompilation -XX:-DoEscapeAnalysis
public static void main(String[] args) {
    for (int i = 0; i < 200; i++) {
        long start = System.nanoTime();
        for (int j = 0; j < 1000; j++) {
            new Object();
        }
        long end = System.nanoTime();
        System.out.printf("%d\t%d\n",i,(end - start));
    }
}

The above code has 200 outer loops and 1000 inner loops, creates 1000 objects, and counts the time of each inner loop.
The operation results are as follows:

0	399200
1	38400
2	34300
3	34900
4	37800
...
61	32600
62	52000
63	14000
64	13400
65	13000
...
109	14100
110	14260
111	16300
112	500
113	600
114	500

It can be seen that the running speed is getting faster and faster because the JVM optimizes the code during program execution.
The JVM divides the execution status into five levels:
0 layer, interpretation execution (Interpreter)
Layer 1, compiled and executed with C1 real-time compiler (without profiling)
Layer 2, compiled and executed using C1 real-time compiler (with basic profiling)
Layer 3, compiled and executed with C1 real-time compiler (with full profiling)
Layer 4, compiled and executed using C2 real-time compiler
profiling refers to collecting data on the execution status of some programs during operation, such as [number of method calls], [number of edge loops], etc. if the code is found to be executed frequently, it can be executed by C2 compiler to thoroughly optimize the hot code.

The difference between just in time compiler (JIT) and interpreter
The interpreter interprets the bytecode as a machine code. Even if the same bytecode is encountered next time, it will still perform repeated interpretation
JIT is to compile some byte codes into machine codes and store them in the Code Cache. When encountering the same code next time, it can be executed directly without
Recompile.
The interpreter interprets bytecode as machine code common to all platforms.
JIT will generate platform specific machine code according to platform type.

For most of the infrequent code, we don't need to spend time compiling it into machine code, but use the way of interpretation and execution
that 's ok; On the other hand, for only a small part of hot code, we can compile it into machine code to achieve the ideal running speed
Degrees. In terms of execution efficiency, simply compare interpreter < C1 (efficiency increased by about 5 times) < C2 (efficiency increased by about 10-100 times). The overall goal is to find hot codes and optimize them.

In the example code, an optimization method called escape analysis is used to find out whether the new object escapes. The newly created object in the code is not used, and the created object does not escape. It is optimized without object creation.

Method Inlining

Sample code

private static int square(final int i) {
	return i * i;
}
public static void main(String[] args) {
	int x = 0;
	for (int i = 0; i < 500; i++) {
		long start = System.nanoTime();
		for (int j = 0; j < 1000; j++) {
			x = square(9);
		} 
		long end = System.nanoTime();
		System.out.printf("%d\t%d\t%d\n",i,x,(end - start));
	}
}

If square is found to be a hot method and its length is not too long, it will be inlined. The so-called inlining is to copy the code in the method
Paste to caller's location:
x = square(9); Replace with x = (9 * 9) and optimize to x = (81)
Code run results

0	81	143600
1	81	30400
2	81	26400
3	81	23100
4	81	25500
...
61	81	21000
62	81	24700
63	81	27200
64	81	9800
65	81	4900
...
218	81	25800
219	81	4600
220	81	0
221	81	0
222	81	100

It can be seen that the code is optimized and the method is inlined.

Field optimization

The so-called field optimization is to optimize the read and write operations of static variables and member variables.

Example code:

@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 1)
@State(Scope.Benchmark)
public class Benchmark1 {
    int[] elements = randomInts(1_000);
    private static int[] randomInts(int size) {
        Random random = ThreadLocalRandom.current();
        int[] values = new int[size];
        for (int i = 0; i < size; i++) {
            values[i] = random.nextInt();
        }
        return values;
    }

    @Benchmark
    public void test1() {
        for (int i = 0; i < elements.length; i++) {
            doSum(elements[i]);
        }
    }

    @Benchmark
    public void test2() {
        int[] local = this.elements;
        for (int i = 0; i < local.length; i++) {
            doSum(local[i]);
        }
    }

    @Benchmark
    public void test3() {
        for (int element : elements) {
            doSum(element);
        }
    }
    
    static int sum = 0;
    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
    static void doSum(int x) {
        sum += x;
    }
    
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(Benchmark1.class.getSimpleName())
                .forks(1)
                .build();
        new Runner(opt).run();
    }
}

Code analysis: there is an array in the class. The data is randomly generated. Test three methods respectively to accumulate and sum the array elements.
Start method inline optimization.
Output results:

Benchmark 			Mode 	Samples 	Score 		  Score error     Units
t.Benchmark1.test1  thrpt         5 	2420286.539   390747.467      ops/s
t.Benchmark1.test2  thrpt         5     2544313.594   91304.136       ops/s
t.Benchmark1.test3  thrpt         5     2469176.697   450570.647      ops/s

It can be seen that the score values of the three methods tested have little difference because they are optimized during method inlining.
For example, in test1:

public void test1() {
	// elements.length will be cached for the first time - > int [] local
	for (int i = 0; i < elements.length; i++) { // Subsequent 999 times of length < - Local
		// Take the element of subscript i for 1000 times < - Local
		sum += elements[i];
	}
}

In method inline optimization, when reading the elements array for the first time, the array is cached locally, and the next time it is read, it is directly read locally. 1999 Field read operations can be saved.

Reflection optimization

Code example:

package cn.itcast.jvm.t3.reflect;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Reflect1 {
	public static void foo() {
		System.out.println("foo...");
	} 
	public static void main(String[] args) throws Exception {
		Method foo = Reflect1.class.getMethod("foo");
		for (int i = 0; i <= 16; i++) {
			System.out.printf("%d\t", i);
			foo.invoke(null);
		} 
		System.in.read();
	}
}

foo.invoke the first 0 ~ 15 calls use the NativeMethodAccessorImpl implementation of MethodAccessor.

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private final Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method var1) {
        this.method = var1;
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
    	// inflationThreshold inflation threshold, default 15
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
        	// The new implementation dynamically generated by ASM is implemented locally, which is about 20 times faster than the local implementation
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }
        // Call local method invoke0
        return invoke0(this.method, var1, var2);
    }
    void setParent(DelegatingMethodAccessorImpl var1) {
        this.parent = var1;
    }
    private static native Object invoke0(Method var0, Object var1, Object[] var2);
}

When the call reaches the 16th time (counting from 0), the class generated at run time will be used instead of the original implementation. The generated class name obtained through debug is sun.reflect.GeneratedMethodAccessor1.
This class is obtained by decompiling using Ali's arthas tool

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
	public Object invoke(Object object, Object[] arrobject) throws InvocationTargetException {
		//If there are parameters, throw illegal parameter exceptions
		block4 : {
			if (arrobject == null || arrobject.length == 0) break block4;
			throw new IllegalArgumentException();
		} 
		try {
			// As you can see, it has been called directly
			Reflect1.foo();
			// Because there is no return value
			return null;
		} 
		catch (Throwable throwable) {
			throw new InvocationTargetException(throwable);
		} 
		catch (ClassCastException | NullPointerException runtimeException) {
			throw new IllegalArgumentException(Object.super.toString());
		}
	}
}

Reflect1.foo() is called directly in the generated class; method. 20 times faster than using the local method invoke0. However, it takes time to generate a class. If it is not called frequently, the class will not be generated and called directly.

Tags: Java jvm

Posted on Fri, 19 Nov 2021 08:09:13 -0500 by jefkin