Deep understanding of JVM -- class loading and bytecode Technology

Posted by anujgarg on Wed, 16 Feb 2022 13:12:01 +0100

Deep understanding of JVM (VII) -- class loading and bytecode Technology (I)

](https://nyimapicture.oss-cn-beijing.aliyuncs.com/img/20200608151300.png)

1. Class file structure

Get first class bytecode file

method:

  • Write java code in the text document (the file name is consistent with the class name), and change the file type to java
  • In the Java terminal, execute javac X:... \ XXX java

The following is a bytecode file

0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09 
0000020 00 16 00 17 08 00 18 0a 00 19 00 1a 07 00 1b 07 
0000040 00 1c 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 
0000060 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e 
0000100 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63 
0000120 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 
0000140 00 04 74 68 69 73 01 00 1d 4c 63 6e 2f 69 74 63 
0000160 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 6f 
0000200 57 6f 72 6c 64 3b 01 00 04 6d 61 69 6e 01 00 16 
0000220 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 
0000240 69 6e 67 3b 29 56 01 00 04 61 72 67 73 01 00 13 
0000260 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 
0000300 6e 67 3b 01 00 10 4d 65 74 68 6f 64 50 61 72 61 
0000320 6d 65 74 65 72 73 01 00 0a 53 6f 75 72 63 65 46 
0000340 69 6c 65 01 00 0f 48 65 6c 6c 6f 57 6f 72 6c 64
0000360 2e 6a 61 76 61 0c 00 07 00 08 07 00 1d 0c 00 1e 
0000400 00 1f 01 00 0b 68 65 6c 6c 6f 20 77 6f 72 6c 64 
0000420 07 00 20 0c 00 21 00 22 01 00 1b 63 6e 2f 69 74 
0000440 63 61 73 74 2f 6a 76 6d 2f 74 35 2f 48 65 6c 6c 
0000460 6f 57 6f 72 6c 64 01 00 10 6a 61 76 61 2f 6c 61 
0000500 6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61 
0000520 2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f 
0000540 75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72 
0000560 69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76 
0000600 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 
0000620 01 00 07 70 72 69 6e 74 6c 6e 01 00 15 28 4c 6a 
0000640 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 
0000660 29 56 00 21 00 05 00 06 00 00 00 00 00 02 00 01 
0000700 00 07 00 08 00 01 00 09 00 00 00 2f 00 01 00 01 
0000720 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0a 00 
0000740 00 00 06 00 01 00 00 00 04 00 0b 00 00 00 0c 00 
0000760 01 00 00 00 05 00 0c 00 0d 00 00 00 09 00 0e 00 
0001000 0f 00 02 00 09 00 00 00 37 00 02 00 01 00 00 00 
0001020 09 b2 00 02 12 03 b6 00 04 b1 00 00 00 02 00 0a 
0001040 00 00 00 0a 00 02 00 00 00 06 00 08 00 07 00 0b 
0001060 00 00 00 0c 00 01 00 00 00 09 00 10 00 11 00 00 
0001100 00 12 00 00 00 05 01 00 10 00 00 00 01 00 13 00 
0001120 00 00 02 00 14

According to the JVM specification, the class file structure is as follows

u4 			 magic
u2             minor_version;    
u2             major_version;    
u2             constant_pool_count;    
cp_info        constant_pool[constant_pool_count-1];    
u2             access_flags;    
u2             this_class;    
u2             super_class;   
u2             interfaces_count;    
u2             interfaces[interfaces_count];   
u2             fields_count;    
field_info     fields[fields_count];   
u2             methods_count;    
method_info    methods[methods_count];    
u2             attributes_count;    
attribute_info attributes[attributes_count];

magic number

u4 magic

0 ~ 3 bytes corresponding to bytecode file

0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

edition

u2 minor_version;

u2 major_version;

0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09

34H = 52, representing JDK8

Constant pool

See information document

... slightly

2. Bytecode instruction

Can refer to

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5

javap tools

Oracle provides javap tools to decompile class files

javap -v F:\Thread_study\src\com\nyima\JVM\day01\Main.class
F:\Thread_study>javap -v F:\Thread_study\src\com\nyima\JVM\day5\Demo1.class
Classfile /F:/Thread_study/src/com/nyima/JVM/day5/Demo1.class
  Last modified 2020-6-6; size 434 bytes
  MD5 checksum df1dce65bf6fb0b4c1de318051f4a67e
  Compiled from "Demo1.java"
public class com.nyima.JVM.day5.Demo1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // hello world
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // com/nyima/JVM/day5/Demo1
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Demo1.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               hello world
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               com/nyima/JVM/day5/Demo1
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public com.nyima.JVM.day5.Demo1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

         8: return
      LineNumberTable:
        line 9: 0
        line 10: 8
}

Graphic method execution process

code

public class Demo3_1 {    
	public static void main(String[] args) {        
		int a = 10;        
		int b = Short.MAX_VALUE + 1;        
		int c = a + b;        
		System.out.println(c);   
    } 
}

Constant pool load runtime constant pool

The constant pool also belongs to the method area, but it is proposed separately here

Method bytecode loading method area

(stack=2, locals=4) the corresponding operand stack has 2 spaces (4 bytes in each space), and there are 4 slots in the local variable table

The execution engine starts executing bytecode

bipush 10

  • Push a byte into the operand stack

    (its length will be supplemented by 4 bytes). Similar instructions include

    • sipush pushes a short into the operand stack (its length will be supplemented by 4 bytes)
    • ldc pushes an int onto the operand stack
    • ldc2_w push a long into the operand stack (push in twice, because long is 8 bytes)
    • Small numbers here exist together with bytecode instructions, and numbers beyond the short range are stored in the constant pool

istore 1

Pop up the top element of the operand stack and put it into slot 1 of the local variable table

In the corresponding code

a = 10

ldc #3

Read the runtime constant pool #3, namely 32768 (the number exceeding the maximum range of short will be put into the runtime constant pool), and load it into the operand stack

Pay attention to short MAX_ Value is 32767, so 32768 = short MAX_ Value + 1 is actually calculated during compilation

istore 2

Pop up the elements in the operand stack and put them in position 2 of the local variable table

iload1 iload2

Put the elements at position 1 and position 2 in the local variable table into the operand stack

  • Because operations can only be performed in the operand stack

iadd

The two elements in the operand stack are popped out of the stack and added, and the result is pushed into the operand stack

istore 3

Pop up the elements in the operand stack and put them in position 3 of the local variable table

getstatic #4

Found #4 in the runtime constant pool, the discovery is an object

Find the object in heap memory and place its reference in the operand stack

iload 3

Push the element at position 3 in the local variable table into the operand stack

invokevirtual 5

Find the constant pool #5 item and locate it in the method area Java / Io / printstream Println: (I) V method

Generate new stack frames (allocate locales, stack, etc.)

Pass the parameters and execute the bytecode in the new stack frame

After execution, pop up the stack frame

Clear the contents of main operand stack

return
Complete the main method call, pop up the main stack frame, and the program ends

Analyze problems through bytecode instructions

code

public class Demo2 {
	public static void main(String[] args) {
		int i=0;
		int x=0;
		while(i<10) {
			x = x++;
			i++;
		}
		System.out.println(x); //Received as 0
	}
}

Why is the final x result 0? It can be known by analyzing bytecode instructions

Code:
     stack=2, locals=3, args_size=1	//Two spaces are allocated to the operand stack and three spaces are allocated to the local variable table
        0: iconst_0	//Prepare a constant 0
        1: istore_1	//Put the constant 0 into slot 1 of the local variable table, i=0
        2: iconst_0	//Prepare a constant 0
        3: istore_2	//Put the constant 0 into slot 2 of the local variable x=0	
        4: iload_1		//Put the number of slot 1 in the local variable table into the operand stack
        5: bipush        10	//Put the number 10 into the operand stack. At this time, there are 2 numbers in the operand stack
        7: if_icmpge     21	//Compare the two numbers in the operand stack. If the lower number is greater than the upper number, jump to 21. The comparison here is to subtract two numbers. Because the operation is involved, the two numbers will be popped out of the operand stack for operation. The operand stack is empty after the operation
       10: iload_2		//Put the number of slot 2 of the local variable into the operand stack, and the value is 0
       11: iinc          2, 1	//Add 1 to the number of slot 2 of the local variable. After self increment, the value in the slot is 1
       14: istore_2	//Put the number in the operand stack into slot 2 of the local variable table, and the value of slot 2 becomes 0
       15: iinc          1, 1 //The value of slot 1 increases by 1
       18: goto          4 //Jump to instruction 4
       21: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       24: iload_2
       25: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
       28: return

Construction method

cinit()V
public class Demo3 {
	static int i = 10;

	static {
		i = 20;
	}

	static {
		i = 30;
	}

	public static void main(String[] args) {
		System.out.println(i); //The result is 30
	}
}

The compiler will collect all static code blocks and static member assignment codes from top to bottom, and merge them into a special method cinit()V:

stack=1, locals=0, args_size=0
         0: bipush        10
         2: putstatic     #3                  // Field i:I
         5: bipush        20
         7: putstatic     #3                  // Field i:I
        10: bipush        30
        12: putstatic     #3                  // Field i:I
        15: return
init()V
public class Demo4 {
	private String a = "s1";

	{
		b = 20;
	}

	private int b = 10;

	{
		a = "s2";
	}

	public Demo4(String a, int b) {
		this.a = a;
		this.b = b;
	}

	public static void main(String[] args) {
		Demo4 d = new Demo4("s3", 30);
		System.out.println(d.a);
		System.out.println(d.b);
	}
}

The compiler will collect all {} code blocks and the code assigned to member variables from top to bottom to form a new construction method, but the code in the original construction method is always last

Code:
     stack=2, locals=3, args_size=3
        0: aload_0
        1: invokespecial #1                  // Method java/lang/Object."<init>":()V
        4: aload_0
        5: ldc           #2                  // String s1
        7: putfield      #3                  // Field a:Ljava/lang/String;
       10: aload_0
       11: bipush        20
       13: putfield      #4                  // Field b:I
       16: aload_0
       17: bipush        10
       19: putfield      #4                  // Field b:I
       22: aload_0
       23: ldc           #5                  // String s2
       25: putfield      #3                  // Field a:Ljava/lang/String;
       //The original construction method is executed at the end
       28: aload_0
       29: aload_1
       30: putfield      #3                  // Field a:Ljava/lang/String;
       33: aload_0
       34: iload_2
       35: putfield      #4                  // Field b:I
       38: return

Method call

public class Demo5 {
	public Demo5() {

	}

	private void test1() {

	}

	private final void test2() {

	}

	public void test3() {

	}

	public static void test4() {

	}

	public static void main(String[] args) {
		Demo5 demo5 = new Demo5();
		demo5.test1();
		demo5.test2();
		demo5.test3();
		Demo5.test4();
	}
}

When calling different methods, the corresponding virtual machine instructions are different

  • Private, constructed and final modified methods all use the invokespecial instruction when calling
  • When ordinary member methods are called, the invokespecial instruction is used. Because the content of this method cannot be determined during compilation, it can only be determined during runtime
  • Static methods use the invokestatic instruction when called
Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class com/nyima/JVM/day5/Demo5 
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: aload_1
         9: invokespecial #4                  // Method test1:()V
        12: aload_1
        13: invokespecial #5                  // Method test2:()V
        16: aload_1
        17: invokevirtual #6                  // Method test3:()V
        20: invokestatic  #7                  // Method test4:()V
        23: return
  • new is to create an object and allocate heap memory to the object. Successful execution will push the object reference into the operand stack
  • dup is the content at the top of the assignment operand stack. This example is [object reference]. Why do you need two references? One is to call the object's construction method "init" in conjunction with invokespecial 😦) V (it will consume one reference at the top of the stack), and the other should cooperate with astore_1 assigned to local variable
  • Fi nal method, private method and construction method are all called by invokespecial instruction and belong to static binding
  • Ordinary member methods are called by invokevirtual and belong to dynamic binding, which supports polymorphic member methods. Another difference between static method calls and polymorphic member methods is whether [object reference] is required before method execution

Polymorphism principle

Because the ordinary member method needs to be run to determine the specific content, the virtual machine needs to call the invokevirtual instruction

When executing the invokevirtual instruction, you go through the following steps

  • First find the object through the reference of the object in the stack frame
  • Analyze the object header and find the actual Class of the object
  • vtable in Class structure
  • Query vtable to find the specific address of the method
  • Bytecode of execution method

exception handling

try-catch
public class Demo1 {
	public static void main(String[] args) {
		int i = 0;
		try {
			i = 10;
		}catch (Exception e) {
			i = 20;
		}
	}
}

Corresponding bytecode instruction

Code:
     stack=1, locals=3, args_size=1
        0: iconst_0
        1: istore_1
        2: bipush        10
        4: istore_1
        5: goto          12
        8: astore_2
        9: bipush        20
       11: istore_1
       12: return
     //One more exception table
     Exception table:
        from    to  target type
            2     5     8   Class java/lang/Exception
  • You can see the structure of an additional Exception table, [from, to) is the detection range of front closed and back open (that is, detect 2 ~ 4 lines). Once there is an exception in the execution of bytecode within this range, match the exception type through type. If it is consistent, enter the line number indicated by target
  • 8-line bytecode instruction astore_2 is to store the exception object reference in position 2 (e) of the local variable table
Multiple single catch
public class Demo1 {
	public static void main(String[] args) {
		int i = 0;
		try {
			i = 10;
		}catch (ArithmeticException e) {
			i = 20;
		}catch (Exception e) {
			i = 30;
		}
	}
}

Corresponding bytecode

Code:
     stack=1, locals=3, args_size=1
        0: iconst_0
        1: istore_1
        2: bipush        10
        4: istore_1
        5: goto          19
        8: astore_2
        9: bipush        20
       11: istore_1
       12: goto          19
       15: astore_2
       16: bipush        30
       18: istore_1
       19: return
     Exception table:
        from    to  target type
            2     5     8   Class java/lang/ArithmeticException
            2     5    15   Class java/lang/Exception
  • Because only one branch of the Exception table can be entered when an exception occurs, the position of slot 2 in the local variable table is shared
finally
public class Demo2 {
	public static void main(String[] args) {
		int i = 0;
		try {
			i = 10;
		} catch (Exception e) {
			i = 20;
		} finally {
			i = 30;
		}
	}
}

Corresponding bytecode

Code:
     stack=1, locals=4, args_size=1
        0: iconst_0
        1: istore_1
        //try block
        2: bipush        10
        4: istore_1
        //After the try block is executed, finally will be executed    
        5: bipush        30
        7: istore_1
        8: goto          27
       //catch block     
       11: astore_2 //The abnormal information is put into slot 2 of the local variable table
       12: bipush        20
       14: istore_1
       //After the catch block is executed, finally will be executed        
       15: bipush        30
       17: istore_1
       18: goto          27
       //If an Exception occurs but is not caught by Exception, other exceptions will be thrown. At this time, you also need to execute the code in the finally block   
       21: astore_3
       22: bipush        30
       24: istore_1
       25: aload_3
       26: athrow  //Throw exception
       27: return
     Exception table:
        from    to  target type
            2     5    11   Class java/lang/Exception
            2     5    21   any
           11    15    21   any

You can see that three copies of the code in "normally" are copied and put into the try process, catch process and the rest of the exception type process of catch

Note: although from the perspective of bytecode instructions, there are finally blocks in each block, the code in the finally block will only be executed once

return in finally
public class Demo3 {
	public static void main(String[] args) {
		int i = Demo3.test();
        //The result is 20
		System.out.println(i);
	}

	public static int test() {
		int i;
		try {
			i = 10;
			return i;
		} finally {
			i = 20;
			return i;
		}
	}
}

Corresponding bytecode

Code:
     stack=1, locals=3, args_size=0
        0: bipush        10
        2: istore_0
        3: iload_0
        4: istore_1  //Temporary return value
        5: bipush        20
        7: istore_0
        8: iload_0
        9: ireturn	//ireturn returns the integer value 20 at the top of the operand stack
       //If an exception occurs, the contents of the finally block will still be executed without throwing an exception
       10: astore_2
       11: bipush        20
       13: istore_0
       14: iload_0
       15: ireturn	//There is no athrow here, that is, if there is a return operation in the finally block and an exception occurs in the try block, the exception will be swallowed!
     Exception table:
        from    to  target type
            0     5    10   any
  • Since ireturn in "nally" is inserted into all possible processes, the returned results must be subject to "nally"
  • As for line 2 in bytecode, it seems useless. Let's leave a hint. Let's see the next example
  • Compared with "normally" in the above example, it is found that there is no athrow, which tells us that if a return occurs in "normally", the exception will be swallowed
  • So don't return in finally
Swallowed anomaly
public class Demo3 {
   public static void main(String[] args) {
      int i = Demo3.test();
      //The final result is 20
      System.out.println(i);
   }

   public static int test() {
      int i;
      try {
         i = 10;
         //An exception should be thrown here
         i = i/0;
         return i;
      } finally {
         i = 20;
         return i;
      }
   }
}

You will find that the print result is 20 and no exception is thrown

finally without return
public class Demo4 {
	public static void main(String[] args) {
		int i = Demo4.test();
		System.out.println(i);
	}

	public static int test() {
		int i = 10;
		try {
			return i;
		} finally {
			i = 20;
		}
	}
}

Corresponding bytecode

Code:
     stack=1, locals=3, args_size=0
        0: bipush        10
        2: istore_0 //Assigned to i 10
        3: iload_0	//Load to top of operand stack
        4: istore_1 //Load into position 1 of local variable table
        5: bipush        20
        7: istore_0 //Assigned to i 20
        8: iload_1 //Load the number 10 at position 1 of the local variable table to the operand stack
        9: ireturn //Return operand stack top element 10
       10: astore_2
       11: bipush        20
       13: istore_0
       14: aload_2 //Loading exception
       15: athrow //Throw exception
     Exception table:
        from    to  target type
            3     5    10   any

Synchronized

public class Demo5 {
	public static void main(String[] args) {
		int i = 10;
		Lock lock = new Lock();
		synchronized (lock) {
			System.out.println(i);
		}
	}
}

class Lock{}

Corresponding bytecode

Code:
     stack=2, locals=5, args_size=1
        0: bipush        10
        2: istore_1
        3: new           #2                  // class com/nyima/JVM/day06/Lock
        6: dup //Copy one and put it at the top of the operand stack for constructor consumption
        7: invokespecial #3                  // Method com/nyima/JVM/day06/Lock."<init>":()V
       10: astore_2 //The rest is placed in position 2 of the local variable table
       11: aload_2 //Load to operand stack
       12: dup //Copy a copy and put it on the operand stack for consumption when locking
       13: astore_3 //Pop up the top element of the operand stack and temporarily store it in slot 3 of the local variable table. At this time, there is a reference to the object in the operand stack
       14: monitorenter //Lock
       //Operation in code block after locking    
       15: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       18: iload_1
       19: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
       //Load the reference of slot 3 object in the local variable table for unlocking    
       22: aload_3    
       23: monitorexit //Unlock
       24: goto          34
       //Abnormal operation    
       27: astore        4
       29: aload_3
       30: monitorexit //Unlock
       31: aload         4
       33: athrow
       34: return
     //It can be seen that whenever an exception occurs, it will jump to line 27, put the exception into the local variable, unlock it, load the exception and throw the exception.      
     Exception table:
        from    to  target type
           15    24    27   any
           27    31    27   any

Topics: Java jvm jar