An in depth understanding of the Java class loading process

  • 2020-06-12 08:55:12
  • OfStack

Java class loading process

There are four stages in the life process of an java file from being loaded to being unloaded:

The load - > Link (verify + prepare + parse) - > Initialization (preparation before use) - > Use - > uninstall

jvm is fully responsible for the process of loading (except custom loading) + linking. When to initialize the class (loading + linking has been completed before this), jvm has strict rules (4 cases) :

1. When encountering new, getstatic, putstatic, invokestatic four bytecode instructions, initialize the class immediately before adding it. There are three situations: when you instantiate a class with new, when you read or set static fields of the class (not including static fields decorated by final because they are already crammed into the constant pool), and when you execute static methods.

2. When calling a class by reflection using the java.lang.reflect. * method, if the class has not already been initialized, do so immediately.

3. When initializing a class, if its father has not been initialized, its father is initialized first.

4. When jvm starts, the user needs to specify a main class (the one containing static void main(String[] args) to execute, then jvm initializes the class first.

The above four pretreatments are called active references to a class, and the rest, called passive references, do not trigger class initialization. Here are some examples of passive quoting:


/** 
 *  Passive reference scenario 1 
 *  Referring to a static field of a parent class by a subclass does not cause the subclass to be initialized  
 * @author volador 
 * 
 */ 
class SuperClass{ 
  static{ 
    System.out.println("super class init."); 
  } 
  public static int value=123; 
} 
 
class SubClass extends SuperClass{ 
  static{ 
    System.out.println("sub class init."); 
  } 
} 
 
public class test{ 
  public static void main(String[]args){ 
    System.out.println(SubClass.value); 
  } 
   
} 

The output is: super class init.


/** 
 *  Passive reference scenario 2 
 *  A class is referenced by an array reference and does not trigger initialization of this class  
 * @author volador 
 * 
 */ 
public class test{ 
  public static void main(String[] args){ 
    SuperClass s_list=new SuperClass[10]; 
  } 
} 

Output result: No output


/** 
 *  Passive reference scenario 3 
 *  Constants are stored in the constant pool of the calling class at compile time and are not intrinsically referenced to the constant-defining class, so the initialization of the constant-defining class is not triggered  
 * @author root 
 * 
 */ 
class ConstClass{ 
  static{ 
    System.out.println("ConstClass init."); 
  } 
  public final static String value="hello"; 
} 
 
public class test{ 
  public static void main(String[] args){ 
    System.out.println(ConstClass.value); 
  } 
} 

hello (tip: at compile time, ConstClass.value has been converted to hello constant and put into the constant pool of test class)

Above is the initialization for the class, the interface also needs to be initialized, the initialization of the interface is a little different from the initialization of the class:

The above code USES static{} to output initialization information. The interface cannot do this, but the compiler will still generate one for the interface when the interface is initialized < clinit > The () class constructor is used to initialize the member variables in the interface, which is also done in class initialization. Really different place is 3 points, class initialization requirements before implementation of the parent class to initialize all done, the initialization of the interface looks for the parent interface initialization is not cold, that is to say, the child interface initialization time does not require the parent interface completes initialization, also only in real use to the parent interface when it can be initialized (such as time constants on the reference interface).

The following is a breakdown of the loading process of the next 1 class: loading - > Validation - > Prepare - > Resolution - > Initialize the

The first is loading:

This virtual machine does three things:

1. Gets the base 2 byte stream defining the class by the fully qualified name of the class.

2. Convert the static storage structure represented by this byte stream into the runtime data structure of the method area.

3. Generate an java. lang. Class object representing this class in the java heap as an entry to the method area for this data.

On the first point, which is very flexible, a lot of the technology comes in here, because it doesn't define where the base 2 stream comes from:

Come from class file - > 1 like file loading

From the zip package - > Load the classes in jar

From the network > Applet

..........

Compared to other stages of the loading process, the loading stage is the most controllable, because the class loader can be written by the system or by itself, and the program can write the loader in its own way to control the byte stream acquisition.

After the acquisition of the base 2 stream is completed, it is saved in the method area in the manner required by jvm, and an ES113en.lang.Class object is instantiated in the java heap to associate the data in the heap.

Once the load is complete, it's time to start checking those byte streams (many steps cross over, such as file format validation) :

Purpose of validation: To ensure that the byte stream information for the class file is to jvm's taste and does not make jvm uncomfortable. If the class file is compiled from pure java code, there will be no unhealthy issues such as array bounds jumping to nonexistent blocks of code, because once this happens, the compiler will refuse to compile. However, like the previous example, the Class file stream may not be compiled from the java source code, it may come from the network or elsewhere, and you can even write it in hexadecimal. If jvm does not validate the data, it may be that some harmful byte stream will bring the jvm down completely.

Validation goes through several steps: file format validation - > Metadata validation - > Bytecode verification - > Symbolic reference verification

File format validation: Verifies that the byte stream conforms to the Class file format specification and that its version can be processed by the current jvm version. After ok is no problem, the byte stream can enter the method area of memory for saving. The next three checks are performed in the method area.

Metadata validation: The information described by bytecode is semantically analyzed to ensure that the described content conforms to the syntax specification of java language.

Bytecode validation: The most complex, checking the contents of the body of a method to make sure it doesn't do anything out of the ordinary at run time.

Symbol reference verification: to verify the authenticity and feasibility of 1 some references, such as the code cited other classes, here to test 1 to see whether those to exist; In other words, the code accesses one of the properties of another class, and the accessible lines of those properties are checked here. (This step will lay the foundation for the rest of the parsing work)

The validation phase is important, but not necessary. If some code is repeatedly used and proven to be reliable, the implementation phase can attempt to turn off most class validation measures with the -ES148en :none parameter to shorten the class load time.

Then, with the above steps completed, the preparation phase begins:

This phase allocates memory for class variables (those that are static) and sets the initial value analogous to that of the car, which is allocated in the method area. This step sets only one initial value to those static variables that are assigned when the object is instantiated. Here, the initial value of class variables is a little different from the assignment of class variables, such as the following:


public static int value=123;

In this phase, the value of value will be 0 instead of 123, because at this point no java code has been executed, 123 is still not visible, and the putstatic instruction that assigns the value of 123 to value is the one that exists after the program is compiled < clinit > (), so assigning a value of 123 to value is performed at initialization time.

Here's an exception:


public static final int value=123;

Here the value of value is initialized to 123 during the preparation phase. This means that at compile time, javac will generate an ConstantValue attribute for this particular value, and in preparation, jm will assign value based on the value of the ConstantValue.

Once you've done that, you're ready to parse. The parsing seems to be a transformation of the fields, methods, etc. of the class, which specifically involves the format content of the Class file, but does not go into details.

The initialization process is the last step of the classloading process:

In the previous class loading process, the user can participate in the loading stage through the custom class loader, the other actions are completely led by jvm, until the initialization phase, the actual execution of the code in java.

This step will perform 1 of the pre-operations. Note that in the preparatory phase, the class variable has been assigned to the system once.

In a nutshell, this step is the execution of the program < clinit > (a); The process of a method. So let's do 1 < clinit > () method:

< clinit > () methods, called class constructor methods, are combined by the compiler to automatically assign all class variables in the class and the statements in the static statement block, in the same order as in the source file.

< clinit > (a); Methods are not the same as class constructors; they do not need to be displayed calling the parent class < clinit > (a); Method, the virtual machine guarantees the subclass < clinit > (a); Method is the first method in the virtual machine to be executed < clinit > (a); The method must be of class ES215en.lang.Object.

Here is an example to illustrate 1:


static class Parent{ 
  public static int A=1; 
  static{ 
    A=2; 
  } 
} 
static class Sub extends Parent{ 
  public static int B=A; 
} 
public static void main(String[] args){ 
  System.out.println(Sub.B); 
} 

First the static data is referenced in ES223en.B and the Sub class is initialized. At the same time, the parent class Parent initializes first. After Parent is initialized, A=2, so B=2; The previous process is equivalent to:


static class Parent{ 
  <clinit>(){ 
    public static int A=1; 
    static{ 
      A=2; 
    } 
  } 
} 
static class Sub extends Parent{ 
  <clinit>(){ //jvm It will let the method of the parent class finish executing here  
  public static int B=A; 
  } 
} 
public static void main(String[] args){ 
  System.out.println(Sub.B); 
} 

< clinit > (a); Methods are not required for classes or interfaces, adding classes or interfaces with no assignment of class variables and no static code blocks, < clinit > () method will not be generated by the compiler.

Since static blocks such as static{} cannot exist in the interface, it is still possible to assign variables to variables when they are initialized, so it is also generated in the interface < clinit > () Constructor. But unlike a class, it executes a subinterface < clinit > (a); The parent interface does not need to be executed before the method< clinit > (a); Method, the parent interface is initialized when a variable defined in the parent interface is used.

In addition, the implementation class of the interface does not execute the interface when it is initialized < clinit > (a); Methods.

In addition, jvm guarantees one class < clinit > (a); Method can be properly locked and synchronized in multithreaded environment. < Because the initialization will only be performed once > .

Here is an example to illustrate 1:


public class DeadLoopClass { 
 
  static{ 
    if(true){ 
    System.out.println(" Want to be  ["+Thread.currentThread()+"]  Initialize the , The following to 1 Infinite cycles "); 
    while(treu){}   
    } 
  } 
   
  /** 
   * @param args 
   */ 
  public static void main(String[] args) { 
    // TODO Auto-generated method stub 
    System.out.println("toplaile"); 
    Runnable run=new Runnable(){ 
 
      @Override 
      public void run() { 
        // TODO Auto-generated method stub 
        System.out.println("["+Thread.currentThread()+"]  I'm going to instantiate that class "); 
        DeadLoopClass d=new DeadLoopClass(); 
        System.out.println("["+Thread.currentThread()+"]  The initialization of that class is done "); 
         
      }}; 
       
      new Thread(run).start(); 
      new Thread(run).start(); 
  } 
 
} 

In this case, you'll see a block when you run it.

Thank you for reading, I hope to help you, thank you for your support to this site!


Related articles: