1 source

  • Source: Java high Concurrency programming in detail multithreading and architecture design, by Wang Wenjun
  • Chapters: Chapters 9, 10 and 11

The notes of these three chapters are sorted out.

2 Introduction to Class Loading

The class loading process can be broken down into three simple stages:

  • Load phase: mainly responsible for finding and loading the binary data files of the class
  • Connection phase: it can be divided into three phases: validation, preparation, and parsing. Validation is to ensure the correctness of the class file, preparation is to allocate memory for static variables of the class and initialize their default values, and parsing is to convert symbolic references in the class to direct references
  • Initialization phase: Assigns correct initial values to static variables of the class

Active and passive use

The JVM specification specifies that each class or interface needs to be initialized the first time it is actively used, specifying the following six scenarios for active class use:

  • throughnewThe keyword causes the initialization of the class
  • Access a static variable of the class
  • Access a static method of a class
  • Perform reflection on a class
  • Initializing a child class causes the parent class to initialize
  • Start the class (that is, containmain()Class) is also initialized

All but the above six cases are called passive use and do not result in class loading and initialization. For example, referencing a static constant of a class does not result in class initialization.

4 Class loading details

As mentioned earlier, class loading can be divided into three simple stages:

  • Loading stage
  • Connection phase
  • Initialization phase

Let’s look at the loading phase first.

4.1 Loading Stage

The loading phase reads the binary data from the class file into memory, converts the static storage structure represented by the byte stream into the runtime data structure in the method area, and generates a java.lang. class object of that class in the heap as an entry point to the method area data structure.

The end product of class loading is the class object in heap memory. The JVM specification states that class loading is to fetch the binary data stream by a fully qualified name. Sources include:

  • classFile: This is the most common format, loadjavacCompiled bytecode files
  • Runtime dynamic generation: for exampleASMIt can be generated dynamically or through dynamic proxiesjava.lang.ProxyGenerate etc.
  • Get it over the Internet: for exampleRMI
  • Read compressed files: for exampleJAR,WARpackage
  • Read from a database: for example, readMySQLIn theBLOBField type data
  • Runtime generationclassFile and load dynamically: for exampleThrift,AvroAnd so on serialization framework, will be aschemaGenerate severalclassFile and load it

At the end of the Class loading phase, the JVM stores these binary byte streams in the method area in a jVM-defined format, forms a specific data structure, and instantiates a Java.lang.class object in heap memory.

4.2 Connection Phase

This stage can be divided into three sub-stages:

  • validation
  • To prepare
  • parsing

It should be noted that these three small stages are not sequential, but cross, that is, there will be a verification process during parsing.

2 verify

The purpose of validation is to ensure that the byte stream contains content that complies with the JVM specification and that there is no code that could jeopardize the JVM’s own security. VerifyError or its subexception is raised when the byte stream information does not meet the requirements.

  • The file format
  • metadata
  • The bytecode
  • Symbolic reference

4.2.1.1 Verifying file Formats

Include:

  • The magic number (0xCAFEBABE)
  • Major and minor version numbers
  • Whether there is incomplete or additional information
  • Whether constant pool constant types are supported
  • Whether a constant pool reference refers to a constant that does not exist or is not supported for a type constant
  • other

4.2.1.2 Verifying Metadata

Metadata validation is actually a process of semantic analysis to ensure that byte streams meet JVM specification requirements, including:

  • Check whether a class has a parent class, whether it inherits an interface, whether those parents or interfaces are valid, or whether they exist
  • Check whether it is inheritedfinalThe class of
  • Check the abstract class to see if the abstract or interface methods of the parent class are implemented
  • Check for overloading, such as the same method name, same parameter but different return type, which is not allowed

4.2.1.3 Verifying bytecode

Bytecode verification is mainly the control flow of the verification program, including:

  • Ensures that the current thread instruction in the program counter does not jump to an invalid bytecode instruction
  • Ensure that type conversions are legal
  • Ensure that the operation stack type and instruction code in the virtual machine stack are executed correctly at any time
  • Other validation

4.2.1.4 Verifying symbol references

Verify the validity of converting symbolic references to direct references to ensure the smooth execution of parsing actions, including:

  • Whether the fully qualified name of the string described by symbolic reference can be found successfully for the related class
  • Whether classes, fields, and methods in symbolic references are visible to the current class
  • other

4.2.2 to prepare

After validation, the preparation phase, which is relatively simple, is to allocate memory for the static variable of the object and set the initial value, the memory of the class variable will be allocated to the method area. To set an initial value is to give the corresponding class variable a default value of the associated type when it is not set. For example, the initial value of Int is 0 and the initial value of reference is NULL.

Holdings parsing

Parsing is the process of finding symbolic references to classes, fields, interfaces, and methods in a constant pool and replacing them with direct references. Parsing is performed for class interfaces, fields, class methods and interface methods, including:

  • Class interface parsing
  • Field analytical
  • Class method resolution
  • Interface method parsing

4.3 Initialization Phase

The initialization phase is essentially the execution of the < Clinit > method, which is generated during the compile phase, that is, contained in a bytecode file that contains the assignment actions for all class variables and the code to execute the static statement block. On the other hand, unlike constructors, < Clinit > does not require an explicit call to the parent class constructor, and the virtual machine guarantees that the parent class’s < Clinit > method executes first.

It is also important to note that

can only be executed by virtual machines. Virtual machines are also guaranteed to be safe in multiple threads, so a static code block containing loading of other classes can cause a deadlock, as can be seen here.

Class loader

5.1 JVMThree types of core class loaders in

There are three core class loaders in the JVM, which are:

  • Startup class loaders: Startup class loaders are the top-level class loaders that have no parentC++Write, be responsible forJVMCore library loading, such as loading the wholejava.langPackage the classes
  • Extension class loader: The parent of the extension class loader is the boot class loader, which mainly loadsjre/lib/extSubdirectory under the class library, pureJavaImplementation, it isURLClassLoaderA subclass of
  • Application class loader: also called system class loader, responsible for loadingclasspathThe parent of the application class loader is the extension class loader, and it is also the default parent of the custom class loader

5.2 Parent delegation mechanism

When a class loader loads a class, it does not try to load the class directly. Instead, the parent loader first tries to load the class until the parent loader at the top level (starts the class loader). If the parent loader fails to load the class, it tries to load the class itself, as shown below:

Thread context class loader

JDK provides many Service Provider interfaces (SPI), such as JDBC. JDBC only defines the logical relationship between these interfaces, but does not provide specific implementation. In other words, JDBC is completely transparent about the specific implementation of application programs and third-party vendors’ database drivers. The application just needs to program to the interface. But here’s the thing:

  • java.lang.sqlAll interfaces in theJDKThe provided class loader that loads these interfaces is the startup class loader
  • Library drivers from third-party vendors are loaded by the system class loader

Connections, statements, and so on are loaded by the startup class loader due to the parent delegation mechanism, while implementations in third-party JDBC driver packages are not loaded. The key to solving this problem is the use of the thread-context classloader to break the parent delegate mechanism.

For example, the load process of the MySQL driver is loaded through the thread context class loader,

private static Connection getConnection(String url, Properties info, Class
        caller) throws SQLException {
        / /...
        if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
		while(true) {
			/ /...
			if (isDriverAllowed(aDriver.driver, callerCL)) {
			}
		}
		/ /...
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
	/ /...
    try {
        aClass = Class.forName(driver.getClass().getName(), true, classLoader);
    } catch (Exception var5) {
        result = false;
    }
    / /...
    return result;
}
Copy the code

With the ththread context classloader, it becomes a way to start the classloader to delegate the subclass loader to load the implementation, and the JDK itself breaks the parent delegate mechanism. This approach involves almost all SPI loads, including JAXB, JCE, JBI, and so on.