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