0 the introduction
Take this Java code as an example to analyze the life course of a Java program:
interface ClassName {
String getClassName(a);
}
class Company implements ClassName {
String className;
public Company(String className) {
this.className = className;
}
@Override
public String getClassName(a) {
returnclassName; }}public class Main {
public static void main(String[] args) {
String className;
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
className = scanner.next();
Company company = new Company(className);
System.out.println("name="+ company.getClassName()); }}}Copy the code
This code covers interfaces, inheritance, object instantiation, and the main method. It is worth the effort to understand what happens at the JVM level from compile to run to the end of the program.
1 Compilation Phase
This code generates three.class files.
The.class file stores magic numbers, version symbols, constant pools, method flags, class indexes, superclass indexes, interface indexes, field tables (which may contain attribute tables), method tables (which may contain attribute tables), and other information. We can clearly describe all the information about classes in Java source code through bytecode files.
Using the Main class as an example, use the javap command to take a look at the generated.class file.
javap -verbose Main;
Copy the code
Last modified 2017-12-13; size 852 bytes MD5 checksum 0336fa14cc04a9c858c34cc016880c19 Compiled from "Main.java" public class Main minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #18.#29 // java/lang/Object."<init>":()V #2 = Class #30 // java/util/Scanner #3 = Fieldref #31.#32 // java/lang/System.in:Ljava/io/InputStream; #4 = Methodref #2.#33 // java/util/Scanner."<init>":(Ljava/io/InputStream;) V #5 = Methodref #2.#34 // java/util/Scanner.hasNext:()Z #6 = Methodref #2.#35 // java/util/Scanner.next:()Ljava/lang/String; #7 = Class #36 // Company #8 = Methodref #7.#37 // Company."<init>":(Ljava/lang/String;) V #9 = Fieldref #31.#38 // java/lang/System.out:Ljava/io/PrintStream; #10 = Class #39 // java/lang/StringBuilder #11 = Methodref #10.#29 // java/lang/StringBuilder."<init>":()V #12 = String #40 // name= #13 = Methodref #10.#41 // java/lang/StringBuilder.append:(Ljava/lang/String;) Ljava/lang/StringBuilder; #14 = Methodref #7.#42 // Company.getClassName:()Ljava/lang/String; #15 = Methodref #10.#43 // java/lang/StringBuilder.toString:()Ljava/lang/String; #16 = Methodref #44.#45 // java/io/PrintStream.println:(Ljava/lang/String;) V #17 = Class #46 // Main #18 = Class #47 // java/lang/Object #19 = Utf8 <init> #20 = Utf8 ()V #21 = Utf8 Code #22 = Utf8 LineNumberTable #23 = Utf8 main #24 = Utf8 ([Ljava/lang/String;)V #25 = Utf8 StackMapTable #26 = Class #30 // java/util/Scanner #27 = Utf8 SourceFile #28 = Utf8 Main.java #29 = NameAndType #19:#20 // "<init>":()V #30 = Utf8 java/util/Scanner #31 = Class #48 // java/lang/System #32 = NameAndType #49:#50 // in:Ljava/io/InputStream; #33 = NameAndType #19:#51 // "<init>":(Ljava/io/InputStream;)V #34 = NameAndType #52:#53 // hasNext:()Z #35 = NameAndType #54:#55 // next:()Ljava/lang/String; #36 = Utf8 Company #37 = NameAndType #19:#56 // "<init>":(Ljava/lang/String;)V #38 = NameAndType #57:#58 // out:Ljava/io/PrintStream; #39 = Utf8 java/lang/StringBuilder #40 = Utf8 name= #41 = NameAndType #59:#60 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #42 = NameAndType #61:#55 // getClassName:()Ljava/lang/String; #43 = NameAndType #62:#55 // toString:()Ljava/lang/String; #44 = Class #63 // java/io/PrintStream #45 = NameAndType #64:#56 // println:(Ljava/lang/String;)V #46 = Utf8 Main #47 = Utf8 java/lang/Object #48 = Utf8 java/lang/System #49 = Utf8 in #50 = Utf8 Ljava/io/InputStream; #51 = Utf8 (Ljava/io/InputStream;)V #52 = Utf8 hasNext #53 = Utf8 ()Z #54 = Utf8 next #55 = Utf8 ()Ljava/lang/String; #56 = Utf8 (Ljava/lang/String;)V #57 = Utf8 out #58 = Utf8 Ljava/io/PrintStream; #59 = Utf8 append #60 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #61 = Utf8 getClassName #62 = Utf8 toString #63 = Utf8 java/io/PrintStream #64 = Utf8 println { public Main(); 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 27: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=4, args_size=1 0: new #2 // class java/util/Scanner 3: dup 4: getstatic #3 // Field java/lang/System.in:Ljava/io/InputStream; 7: invokespecial #4 // Method java/util/Scanner."<init>":(Ljava/io/InputStream;)V 10: astore_2 11: aload_2 12: invokevirtual #5 // Method java/util/Scanner.hasNext:()Z 15: ifeq 63 18: aload_2 19: invokevirtual #6 // Method java/util/Scanner.next:()Ljava/lang/String; 22: astore_1 23: new #7 // class Company 26: dup 27: aload_1 28: invokespecial #8 // Method Company."<init>":(Ljava/lang/String;)V 31: astore_3 32: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream; 35: new #10 // class java/lang/StringBuilder 38: dup 39: invokespecial #11 // Method java/lang/StringBuilder."<init>":()V 42: ldc #12 // String name= 44: invokevirtual #13 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 47: aload_3 48: invokevirtual #14 // Method Company.getClassName:()Ljava/lang/String; 51: invokevirtual #13 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 54: invokevirtual #15 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 57: invokevirtual #16 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 60: goto 11 63: return LineNumberTable: line 31: 0 line 32: 11 line 33: 18 line 34: 23 line 35: 32 line 36: 60 line 37: 63 StackMapTable: number_of_entries = 2 frame_type = 253 /* append */ offset_delta = 11 locals = [ top, class java/util/Scanner ] frame_type = 51 /* same */ } SourceFile: "Main.java"Copy the code
Analyze the output from the constant pool directly:
#1 = Methodref #18.#29 // java/lang/Object."<init>":()V
Copy the code
Methodref tells us that the first index in the constant pool represents a method reference, and that the description of the method reference can be described by looking up the 18th and 29th entries in the constant pool. In fact, the corresponding is the content of the comments. It tells you that the class this method belongs to is Object, that its simple name is
, and that its descriptor is ()V, which is the instance constructor in Object (SIMPLE names and descriptors I explained in a previous blog post).
Why, you might ask, does the first item in the Main constant pool describe a parameterless constructor in Object? In Java, all classes inherit from Object, and indexes of their parent classes are kept in the constant pool. In Java, there is a rule for initializing and instantiating objects: initialize and instantiate the parent class first, and then the child class.
The remaining constant pool analysis is similar to the above.
In the code block below the constant pool, you can see that one of the default constructors of the Main class is the Main method. I’ll talk about these two things later in the bytecode execution phase.
As you can see, the.class file contains detailed information, a lot of information that you can’t get directly from the source code.
2 Class loading phase
Javac compiles the source file and then starts running the Main class using the Java command. Java commands can only run classes that contain the main method.
As soon as the Java command runs, the JVM starts loading the Main class.
The JVM when loading the Main class using the class loader delegation model (parents) on the load, load, the validation phase, due to no class variables in the Main class, also is equivalent to skip this stage, and then to parse the bytecode, as the Main method is a static method, also is a virtual method, Begins static linking, resolving symbolic references to the main method directly into direct references in bytecode. Since there are no static variables or static blocks, the initialization phase is skipped, and the whole loading process is completed, and the corresponding Class object of the Main Class is generated in the method area.
Since there is no polymorphism involved in this example, there is no dispatch involved, but be sure to master this.
3 Method execution phase
With all the information in the class loaded in memory, the JVM starts making method calls.
Method invocation: The JVM starts executing the main method, which is done by the bytecode execution engine in the virtual machine. The main method is called by a thread. This thread allocates itself some stack space on the virtual machine stack, and whenever the thread calls a new method, that method is pushed to the top of the virtual machine stack (as the current stack frame). Local variables defined in this method are stored in the local variable table. The JVM does not store the name of the local variable, but instead identifies each different local variable by its relative offset from the local variable table.
The Code property of the method table in the.class file holds the bytecode in the Java method body.
Code:
stack=3, locals=4, args_size=1
Copy the code
As you can see, before the main method is called (actually at compile time, its local variable table and operand stack size are determined), locals is 4, which means the local variable table size is 4: this, className, scanner, company; Stack is 3, the size of the operand stack is 3: className, scanner, company.
We can also see in the bytecode of the method body above a number of instructions that the JVM needs to interpret to run during method execution, as follows:
0: new #2 // class java/util/Scanner
Copy the code
The previous 0 represents a relative offset, while the new directive creates a new object, corresponding to the Java source new, followed by #2, which represents the parameter of the new directive, representing the data item in the constant pool with index 2, which is the Scanner class in the comments following the above code.
It is worth noting that Scanner is an instantiation of a class, so the JVM first determines whether the Scanner class has been loaded into memory. If not, the JVM starts loading the Scanner class, as described above, and then initializes the object.
Company Company = new Company(className); . This is an instantiation of a class that implements the ClassName interface and is instantiated with parameters. So when is the ClassName interface loaded? Based on the information I consulted, I guess that the loading of its interface will be triggered during the verification phase of Company class loading. For details, you can refer to Google and Baidu.
So how to implement parameter passing? During the instantiation of the Company class, the Company constructor is executed, and the constructor itself is a method, so it can be understood that the argument goes into the operand stack of the called method, and then out of the stack assigns values to the local variable table of the called method.
Finally, the code is executed and the program exits.
4 Reference Reading
- JVM internals (vi) – One of the basics of Java bytecode