primers

The Java virtual machine is started by Bootstrap Class Loader to create an Initial Class that is specified by the implementation of the virtual machine. Next, the Java virtual machine links to the initialization class, initializes and calls its public void Main (String[]) method. The rest of the execution begins with a call to this method. Executing Java Virtual machine instructions in the main method may cause the Java VIRTUAL machine to link to additional classes or interfaces, or to call additional methods.

Perhaps on a Java virtual machine implementation, the initial classes are provided to the virtual machine as command-line arguments. Of course, virtual machine implementations can also use an initial class to have the class loader load the entire application in turn. The initial class can also choose to work in a combination of these ways.

The above is from the Java Virtual Machine Specification (Java SE version 7)

Before we talk about the loading mechanism of classes, let’s look at a topic:

Public class ClassLoaderTest {public static void main(String[] args) {system.out.println (" father :" + son.factor); // new Son(); // entry 2}} class Grandpa {static {system.out.println (" Grandpa in static code block "); } public Grandpa() {system.out.println (" I am Grandpa ~"); }} class Father extends Grandpa {static {system.out.println (" Father in static code block "); } public static int factor = 25; Public Father() {system.out.println (" I am Father ~"); }} class extends Father {static {system.out.println (" Son in static code block "); } public Son() {system.out.println (" I am the Son ~"); }}Copy the code

In the above code, entry 1 and entry 2 are divided. They are not the same at the same time, and the final output result is also different. You can think about the difference between these two entries for class initialization. Here are the results:

Results of entry 1:

Grandpa in static code block Dad in static code block Dad's age :25Copy the code

Result of entry 2

Grandpa in static code block Dad in static code block Son in static code block I'm grandpa ~ I'm dad ~ I'm son ~Copy the code

If you haven’t faced this kind of question before, it will be very difficult for you to answer it now. This question examines your understanding of the Java class loading mechanism. If you don’t understand the Java loading mechanism, you won’t be able to solve this problem.

Comparing the above two results, you can see that entry 1 is both static code initialization, and entry 2 involves both static code initialization and class initialization. By now you know that there is a difference between the initialization logic for static and non-static code.

This article will explain the Java class loading mechanism, so that you will not encounter similar problems in the future.

Class loading process

After the Java VIRTUAL machine compiles Java source code into bytecode, the virtual machine can read the bytecode into memory for parsing and running. This process is called the class loading mechanism of the Java Virtual machine. The execution of class bytecode by the JVM virtual machine can be divided into seven stages: load, validate, prepare, parse, initialize, use, and unload. The order of load, verify, prepare, initialize and unload is fixed, while parsing is not. To support dynamic binding, the parsing process can occur after the initialization phase.

 

 

1. The load

When you need to start the first stage of class loading: load. The JAVA Virtual Machine specification does not enforce constraints, leaving the implementation of the virtual machine free rein.

The loading phase is a phase in the class loading process, which is also called “load”. During the loading phase, the VIRTUAL machine mainly does the following three things:

  1. Get the binary byte stream that defines this class by class full name
  2. Transform the static storage structure represented by the byte stream into the runtime data structure of the method area
  3. Generate a java.lang.Class object representing this Class in the Java heap as an access point to the data in the method area (so we can use the.getClass() Class with a low profile).

Note that the byte stream does not have to be retrieved from a Class file, but can be read from ZIP packages (such as JAR packages and WAR packages), computed at run time (dynamic proxies), or generated from other files (such as JSP files converted to the corresponding Class Class). The loaded information is stored in the JVM’s methods area.

The array class does not have a byte stream, but is generated directly by the Java virtual machine. For other classes, the Java virtual machine uses the class loader to find the byte stream.

The load phase is the action of retrieving the binary data (disk or network) of a Class, and putting the data (Class information: Class definition or structure) into the method area (memory).

An illustration:

2. Verify

The main purpose of validation is to ensure that the classes being loaded are correct. It’s also the first step in the connection phase. In other words, the.class file we loaded is not harmful to our virtual machine, so check it first. He mainly completed four stages of verification:

  1. File format verification: Verifies that the.class file byte stream complies with the class file format specification and can be processed by the current version of the VIRTUAL machine. This is mainly used to check magic numbers, major version numbers, constant pool, etc. (magic numbers, major version numbers are.class file data information, here can not understand).
  2. Metadata verification: it mainly conducts semantic analysis on the information described by bytecode to ensure that the information described conforms to the requirements of Java language specifications, such as verifying whether the class has a parent class, and whether the field methods in the class conflict with the parent class, etc.
  3. Bytecode validation: This is the most complex phase of the entire validation process, mainly through data flow and control flow analysis to determine that the program semantics are legitimate and logical. After verifying the data type in the metadata verification stage, this stage mainly analyzes the method of the class to ensure that the method of the class will not make weihai VIRTUAL machine security when running.
  4. Symbolic reference validation: This is the final stage of validation and occurs when the virtual machine converts symbolic references to direct references. It mainly validates information outside the class itself. The goal is to ensure that the parsing action completes.

The validation phase is an important but unnecessary phase for the entire class loading mechanism. If our code ensures that there are no problems, then there is no need to validate it because it takes time. Of course we can use -xverfity: None to turn off most validation.

3. Preparation (key points)

Once the bytecode files have been validated, the JVM starts allocating memory for class variables and initializing them. Two key points to note here are the object that memory is allocated and the type of initialization.

  • Memory allocation object. There are two types of variables in Java: class variable and class member variable. Class variable refers to a variable that is modified static. All other types of variables are class member variables. In the preparation phase, the JVM only allocates memory for “class variables,” not “class member variables.” Memory allocation for class member variables does not begin until initialization.

For example, in the preparation phase, the following code allocates memory only for the Factor attribute, not for the website attribute.

public static int factor = 3;
public String website = "www.cnblogs.com/chanshuyi";
Copy the code
  • The type of initialization. In the preparation phase, the JVM allocates memory for class variables and initializes them. But by initialization, we mean assigning a variable the zero value of that data type in the Java language, not the value initialized in user code.

For example, the following code will have a sector value of 0 instead of 3 after the preparation phase.

public static int sector = 3;
Copy the code

But if a variable is a constant (modified by static final), the property is given the desired value during the preparation phase. For example, after the preparation phase, the value of number will be 3, not 0. \

public static final int number = 3;
Copy the code

Static final is copied directly, and static variables are assigned zero values. We can figure it out with a little thought.

The difference between the two statements is that one has the final keyword modifier and the other does not. The final keyword in Java stands for immutable, meaning that the value of number will not change once it is assigned. Since once a value is assigned it cannot be changed, it must be given the desired value by the user in the first place, so a class variable modified with final is given the desired value in the preparation phase. A class variable that is not modified with final may change during initialization or runtime, so there is no need to give it the desired value during preparation.

4. The parsing

The parsing phase is the process of replacing symbolic references in the virtual machine constant pool with direct references.

Symbolic reference: A symbolic reference is a group of symbols that describe the target object being referenced. The symbol can be any literal, as long as it is used to unambiguously locate the target. Symbolic references are independent of the memory layout implemented by the virtual machine, and the target object referenced is not necessarily already loaded into memory. The Java virtual machine specifies the literal form of symbolic references defined in the Class file format.

Direct reference: A direct reference can be a pointer to the target object, a relative offset, or a handle that can be indirectly located to the target. A direct reference is related to the implementation of virtual machine memory layout. The translation of a symbolic reference on different VIRTUAL machine instances is generally different. If a direct reference exists, the target of the reference must already exist in memory.

In the parsing phase, the parsing action is mainly for the seven symbolic references, whose names and constant types in the constant pool are as follows:

Analytical action | | symbols referenced analytical possible error | | | — — — — — — — — — – | — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — – | ———————————————————–

| | | class or interface CONSTANTClassInfo | Java. The land. The IllegalAccessError

| | | field CONSTANTFieldrefInfo | Java. Land. IllegalAccessError or Java. The land. The NoSuchFieldError

| | | class method CONSTANTMethodefInfo | Java. The land. The IllegalAccessError or Java. Land. NoSuchMethodError

| | | interface methods CONSTANTInterfaceMethoderInfo | Java. The land. The IllegalAccessError or Java. Land. NoSuchMethodError

| | | method type CONSTANTMethodTypeInfo |

| | method handles | CONSTANTMethodhandlerInfo |

| | call qualifier | CONSTANTInvokeDynamicInfo |

The whole stage of parsing in virtual machines is still more complex, much more complex than the above, but many special details we can ignore for the moment, first have a general understanding and understanding of the time to slowly deepen.

5. Initialization (emphasis)

The class initialization stage is the last step in the class loading process. In the class loading process mentioned above, except the user application can participate in the loading stage through the custom class loader, the rest of the actions are dominated and controlled by the virtual machine. The initialization phase is the actual execution of the Java program code (or bytecode) defined in the class.

In the preparation phase, variables are assigned their required initial value (zero) once, while in the initialization phase class variables and other resources are initialized according to a subjective plan made by the programmer through the program. (Or put another way: the initialization phase is the process of executing the class constructor

() method.)

At this stage, the JVM initializes class objects based on the order in which the statements are executed. Generally, initialization is triggered when the JVM encounters one of the following five situations:

  1. New, getStatic, putstatic, and Invokestatic are four bytecode instructions. If the class has not been initialized, it needs to be initialized first. The most common Java code scenarios for generating these four instructions are when an object is instantiated using the new keyword, when static fields of a class (except those that are modified by final and have been put into the constant pool by the compiler) are read or set, and when static methods of a class are called.
  2. When a reflection call is made to a class using the java.lang.Reflect package’s methods, initialization needs to be triggered if the class has not already been initialized.
  3. When initializing a class, if the parent class has not been initialized, the initialization of the parent class must be triggered first.
  4. When the virtual machine starts, the user needs to specify a primary class (the one containing the main() method) to execute, and the virtual machine initializes this primary class first.
  5. When using JDK1.7 dynamic language support, if a Java lang. Invoke. The final analytical results REF_getstatic MethodHandle instance, REF_putstatic, REF_invokeStatic method handles, And the class corresponding to the method handle is not initialized, so it needs to be initialized first.

See the above several conditions you may be dizzy, but it does not matter, do not need to back, know it is good, when used back to find it can be.

Note that the initialization, not the creation of an instance of the class, but the implementation of the class constructor, which simply means initialization of static variables, static code blocks. Constructors are executed only at instance creation time.

6. Use

When the JVM completes the initialization phase, the JVM starts executing the user’s program code from the entry method. This stage is also just understanding can be.

7. Remove

When the user program code is finished executing, the JVM starts destroying the created Class object, and eventually the JVM responsible for running it exits memory. This stage is also just understanding can be.

8. Answer the introductory questions

Remember the previous topic, the following analysis:

Entry 1

Why not output the string “son in static code block”?

This is because for a static field, only the class that directly defines the field is initialized (executes a static code block). Therefore, referring to a static field defined in a parent class by its subclass only triggers initialization of the parent class but not of the subclass.

For the example above, we can start at the entrance and work our way down:

  • First the program goes to the main method and uses the normalized output of the Factor class member variable from the Son class, which is not defined in the Son class. So we go to the parent class, we find the corresponding class member variable in Father, and that triggers the initialization of Father.
  • But according to the third of the five initialization cases we mentioned above (when initializing a class, if it is found that its parent has not been initialized, it needs to trigger the initialization of its parent first). We need to initialize the Father class, that is, the grandfather class and then the Father class. So we initialize the Grandpa class output: “Grandpa is in a static code block” and then initialize the Father class output: “Dad is in a static code block”.
  • Finally, after all the parent classes have been initialized, Son can call the static variable of the parent class, printing “Dad age: 25”.

The entry 2

Here we use new to initialize, so we initialize the parent class first. Static variable initialization is performed first. When a subclass creates an object, it creates an object of its parent class first, so it must call the constructor of the parent class first.

change

Here I made a few changes:

Public class ClassLoaderTest {public static void main(String[] args) {// system.out.println (" father's age :" + son.factor); // 1 new Son(3); }} class Grandpa {int s = 3; Public Grandpa(int s) {system.out.println (" I am Grandpa ~"); } static {system.out.println (" grandparent in static code block "); }} class Father extends Grandpa {static {system.out.println (" Father in static code block "); } public static int factor = 25; public Father(int s) { //super(s); System.out.println(" I am dad ~"); }} class extends Father {static {system.out.println (" Son in static code block "); } public Son(int s ) { super(s); System.out.println(" I am the son ~"); }}Copy the code

When initializing the subclass, the parent class constructor is not explicitly called, and the result is as follows:

Exception in thread "main" java.lang.Error: Exception in thread "main" java.lang.Error: Java.lang. Unresolved compilation problem: Implicit super constructor Grandpa() is undefined. Must explicitly invoke another constructor at Father.<init>(ClassLoaderTest.java:27) at Son.<init>(ClassLoaderTest.java:39) at ClassLoaderTest.main(ClassLoaderTest.java:5)Copy the code

In simple terms, if the subclass constructor does not show the constructor calling the parent class, then when initializing the child class, the parent class will look for the no-argument constructor. If the parent class defines only the constructor with arguments and no no-argument constructor, an error will be reported. Therefore, it is generally better to display the call, or to define several different constructors that can be invoked in different scenarios.

Class loader

Pass the class-loading phase of fetching the binary stream describing a class by its fully qualified name to a class-loader outside the virtual machine. The advantage of this is that we can implement our own class loader to load classes in other formats, as long as they are binary byte streams, which greatly increases the flexibility of the loader.

Class loaders of the system are divided into three types:

  1. Start the class loader. The other classloaders are subclasses of java.lang.ClassLoader. The launcher ClassLoader is implemented by C++ and has no corresponding Java object, so it can only be replaced by null in Java. Start the class loader to load the most basic and important classes, such as the jar classes in the LIB directory of the JRE. The parent of the extension class loader is the launcher class loader, which is responsible for loading relatively minor but generic classes, such as those in jar packages in the LIB /ext directory of the JRE
  2. Extend the class loader. Java core class library, responsible for loading Java extension libraries (load classes in JAVA_HOME/jre/ext/*.jar), developers can directly use the extension class loader.
  3. Application class loader. The Java core class library provides. The parent of the application class loader is the extension class loader, which is responsible for loading classes under the application path. Developers can use this class loader directly. If the application does not define its own class loader, it loads the classes of Java applications.

The specific relationship is as follows:

 

 

How the parent delegation mechanism works:

If a class loader receives a request from a class loader, it first does not attempt to load the class itself, but delegates the request to the parent loader. This is true for each level of class loaders, so all load requests will eventually be passed to the Bootstrap class loader (the Bootstrap loader), and the child loader will try to load the request itself only if the parent class loader reports that it cannot load the request (it did not find the required class in its search scope).

Advantages of the parent delegate model: Java classes have a hierarchy of priorities along with their loaders.

For example, the java.lang.Object class is stored in rt.jart and must be loaded by any classloader. The parent delegates the Bootstrap class loader at the top of the model. Thus, the Object class is the same class in the various classloader environments of the program. Conversely, if you don’t use the parent delegate model. Each class loader is loaded by itself. If the user writes a class called “java.lang.Object” and places it in the program’s ClassPath. There would be multiple Object classes, the most basic behavior in the Java type system would not be guaranteed, and the application would be in chaos.

This can also be verified with code:

public class ClassLoaderTest { public static void main(String[] args) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); System.out.println(loader); System.out.println(loader.getParent()); System.out.println(loader.getParent().getParent()); }}Copy the code

The output is:

sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@7852e922
null
Copy the code

The previous description is consistent. The launcher class loader is implemented by C++ and has no corresponding Java object, so it can only be replaced by null in Java.

Custom class loaders

1. Why custom ClassLoader

If you want to load your own class file, you can create a custom ClassLoader.

And we can encrypt and decrypt class files according to our own needs.

2. How to customize a ClassLoader

Create a new class that inherits from java.lang.ClassLoader and overrides its findClass method. Converts a class bytecode array to an instance of a class class. Call the loadClass method to load

Code combat:

Start by defining a custom classloader

package com.hello.test; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; Public class MyClassLoader extends ClassLoader {private String path; public MyClassLoader(String classPath){ path=classPath; } /** * Override findClass method * @param name is the full path of our Class * @return * @throws ClassNotFoundException */ @override protected Class<? > findClass(String name) throws ClassNotFoundException { Class log = null; Byte [] classData = getData(); if (classData ! Log = defineClass(name, classData, 0, classdata.length); } return log; } @return */ private byte[] getData() {File File = new File(path); if (file.exists()){ FileInputStream in = null; ByteArrayOutputStream out = null; try { in = new FileInputStream(file); out = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int size = 0; while ((size = in.read(buffer)) ! = -1) { out.write(buffer, 0, size); } } catch (IOException e) { e.printStackTrace(); } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } return out.toByteArray(); }else{ return null; }}}Copy the code

You can do a lot of things in getData, such as encryption and decryption.

Create a test class:

package com.hello.test; public class Log { public static void main(String[] args) { System.out.println("load Log class successfully from log " ); }}Copy the code

Javac log.java generates our log.class file:

Finally, load:

package com.hello.test; import java.lang.reflect.Method; Public class ClassLoaderTest {public static void main(String[] args) {public static void main(String[] args) {public static void main(String[] args) { / Users/yourname/Documents/workspace - STS - 3.9.6. RELEASE/HelloWorld/SRC/the class "; MyClassLoader myClassLoader = new MyClassLoader(classPath); // The full name of the class, corresponding to the package name String packageNamePath = "com.hello.test.log "; Try {// load the Log file class <? > Log = myClassLoader.loadClass(packageNamePath); System.out.println(" class loader is :" + log.getClassLoader ()); Method = log.getDeclaredMethod ("main", String[].class); Object object = Log.newInstance(); String[] arg = {"ad"}; method.invoke(object, (Object) arg); } catch (Exception e) { e.printStackTrace(); }}}Copy the code

The following output is displayed:

You can see that the load is delegated to the parent class. That’s it for classloaders.

Strengthen practice

Finally, let’s look at an upgraded topic:

public class Book { static int amount1 = 112; static Book book = new Book(); Public static void main(String[] args) {staticFunction(); } static {system.out.println (" book static code block "); } {system.out.println (" ordinary block of book code "); } Book() {system.out.println (" Book constructor "); System.out.println("price=" + price +", amount=" + amount + ", amount1=" + amount1); } public static void staticFunction() {system.out.println (" staticFunction ");Copy the code
System.out.println("amount=" + amount + ",amount1=" + amount1);Copy the code
} int price = 110; static int amount = 112; // static Book book = new Book(); // entry 2}Copy the code

The result of entry 1

The normal block book constructor (price=110, amount=0, amount1=112

Result of entry 2

The static code block constructor for the book price=110, amount=112, amount1=112

Entrance 1 analysis

In both cases, we simply ignored the initialization of the main class because there was no extra code in the class.

But in this case, there’s a lot of code in the main class, so we can’t just ignore it.

  1. When the JVM is in the preparation phase, memory is allocated and initialized for class variables. At this point, our book instance variable is initialized to null, and the amount and amout1 variables are initialized to 0.
  2. When entering the initialization phase, because the Book method is the entry point to the program, according to the fourth of the five cases of class initialization we discussed above (when the virtual machine starts, the user needs to specify a main class to execute (the one containing the main() method), the virtual machine initializes this main class first). So the JVM initializes the Book class, which executes the class constructor.
  3. The JVM initializes the Book class by executing the class constructor (which consists of collecting all the static code blocks and class variable assignments in sequence),
  4. The constructor of the object is then executed (member variable assignments and ordinary code blocks are collected in sequence, and object constructors are finally collected to form object constructors).

For entry 1, the execution-class constructor finds that the book instance is a static variable and executes the normal code block before executing the book constructor. After execution, go back to executing the class constructor and initialize the rest of the static variables.

Entry 2 analysis

The change in entry 2 is to move static instance initialization to the end. This ensures that the class constructor is executed first before the object initialization process.

variation

What would happen if we commented out all entries 1,2:

The static code block for the book the static method amount=112, amount1=112Copy the code

As you can see, only the class constructor ends up being executed.

methodology

As can be seen from the above examples, the following steps can be used to analyze the execution order of a class:

  • Determines the initial value of a class variable. In preparation for class loading, the JVM initializes a zero value for a class variable, which has an initial zero value. If it is a class variable decorated with final, it is initialized directly to the value the user wants.
  • Initialize the entry method. After entering the initialization phase of class loading, the JVM looks for the entire main method entry and initializes the entire class that the main method is in. When a class needs to be initialized, the class constructor () is initialized first, followed by the object constructor ().
  • Initializes the class constructor. The JVM sequentially collects assignment statements, static code blocks, and eventually class constructors to be executed by the JVM.
  • Initializes the object constructor. The JVM collects member variable assignment statements, ordinary code blocks, and finally constructors into object constructors that are executed by the JVM.

If an initialization of another class is encountered while initializing the class of the main method, the corresponding class is loaded first and returned when the load is complete. This loop repeats until the class of main is returned.

 

Refer to the article

Class loading mechanisms – In-depth understanding of the JVM

Do you understand the Java class loading mechanism?

Lecture 7: The JVM classloading mechanism

Java Memory Management – Mastering the VM Class loading mechanism (4)