綜上篇介紹,下面來詳細講解java代碼的執行結果
首先看源代碼 ,通常當我們運行下面代碼時,我們會認爲虛擬器會創建一個線程,從main方法開始執行。然後計算abc的值後開始調用getN()方法。
package com.company;
public class Test1 {
static int n = 5;
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = a + b;
System.out.println(c);
getN();
}
public static void getN(){
n++;
System.out.println(n);
}
}
我們再來看.class文件是怎麼操作的
.class文件我們很難看懂 不過可以使用javap指令進行反編譯 (密集恐懼症勿入)
先貼代碼 再來講解。基本的我已備註,可以先理解下
Classfile /C:/Users/maxc/Desktop/Test/src/com/company/Test1.class
Last modified 2020-5-6; size 580 bytes
MD5 checksum 54acc5781fba7440ba0aae6f9652f592
Compiled from "Test1.java"
public class com.company.Test1
minor version: 0 //最低版本號
major version: 52 //版本號
flags: ACC_PUBLIC, ACC_SUPER //標識靜態 公共方法
Constant pool: //常量池
#1 = Methodref #7.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #23.#24 // java/io/PrintStream.println:(I)V
#4 = Methodref #6.#25 // com/company/Test1.getN:()V
#5 = Fieldref #6.#26 // com/company/Test1.n:I
#6 = Class #27 // com/company/Test1
#7 = Class #28 // java/lang/Object
#8 = Utf8 n
#9 = Utf8 I
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 getN
#17 = Utf8 <clinit>
#18 = Utf8 SourceFile
#19 = Utf8 Test1.java
#20 = NameAndType #10:#11 // "<init>":()V
#21 = Class #29 // java/lang/System
#22 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#23 = Class #32 // java/io/PrintStream
#24 = NameAndType #33:#34 // println:(I)V
#25 = NameAndType #16:#11 // getN:()V
#26 = NameAndType #8:#9 // n:I
#27 = Utf8 com/company/Test1
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (I)V
{
static int n;
descriptor: I
flags: ACC_STATIC //定義靜態變量n
public com.company.Test1(); //構造器?
descriptor: ()V
flags: ACC_PUBLIC //定義公共方法
Code:
stack=1, locals=1, args_size=1 //棧深度爲1 局部變量數1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1 //向操作數棧壓入1
1: istore_1 //將值壓出操作棧 並存儲爲索引爲1的變量中 相當於 a = 1
2: iconst_2 //向操作數棧壓入2
3: istore_2 //將值壓出操作棧 並存儲爲索引爲2的變量中 相當於 b = 2
4: iload_1 //向操作數棧壓入索引爲1的變量值
5: iload_2 //向操作數棧壓入索引爲2的變量值
6: iadd //將操作棧的值相加
7: istore_3 //將值壓出操作棧 並存儲爲索引爲3的變量中 相當於 c = 1
8: getstatic #2 //獲取流方法 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_3 //向操作數棧壓入索引爲3的變量值
12: invokevirtual #3 //打印 // Method java/io/PrintStream.println:(I)V
15: invokestatic #4 //調用方法getN // Method getN:()V
18: return
LineNumberTable: //Java源碼行號和字節碼指令的對應關係
line 8: 0
line 9: 2
line 10: 4
line 11: 8
line 12: 15
line 13: 18
public static void getN();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #5 //讀取靜態數n = 5 // Field n:I
3: iconst_1 //向操作數棧壓入1
4: iadd //將操作數棧數值相加
5: putstatic #5 //設置n = 6 // Field n:I
8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
11: getstatic #5 // Field n:I
14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
17: return
LineNumberTable:
line 16: 0
line 17: 8
line 18: 17
static {}; //靜態方法
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: iconst_5 //向操作數棧壓入5
1: putstatic #5 //設置n = 5 // Field n:I
4: return
LineNumberTable:
line 5: 0
}
首先 jvm虛擬機會將.class文件讀取進方法區位置,並將
常量池會載入:上圖中Constant pool
例如:#1 = Methodref #7.#20 // java/lang/Object."<init>":()V
第一列:標識類型爲Methodref的常量
第二列:常量項的引用 你可以一直跟着往下面找,最後得到第三列 // java/lang/Object.com/company/Test1."<init>":()V
方法元會載入:
public static void main(java.lang.String[]);
public static void getN();
static {}; //靜態方法
類元 會載入:但不限於以下信息
Classfile /C:/Users/maxc/Desktop/Test/src/com/company/Test1.class
Last modified 2020-5-6; size 580 bytes
MD5 checksum 54acc5781fba7440ba0aae6f9652f592
Compiled from "Test1.java"
public class com.company.Test1
minor version: 0 //最低版本號
major version: 52 //版本號
flags: ACC_PUBLIC, ACC_SUPER //標識靜態 公共方法
然後jvm 會從main方法開始執行程序,先來看一下main方法的內容
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1 //向操作數棧壓入1
1: istore_1 //將值壓出操作棧 並存儲爲索引爲1的變量中 相當於 a = 1
2: iconst_2 //向操作數棧壓入2
3: istore_2 //將值壓出操作棧 並存儲爲索引爲2的變量中 相當於 b = 2
4: iload_1 //向操作數棧壓入索引爲1的變量值
5: iload_2 //向操作數棧壓入索引爲2的變量值
6: iadd //將操作棧的值相加
7: istore_3 //將值壓出操作棧 並存儲爲索引爲3的變量中 相當於 c = 1
8: getstatic #2 //獲取流方法 // Field java/lang/System.out:Ljava/io/PrintStream;
11: iload_3 //向操作數棧壓入索引爲3的變量值
12: invokevirtual #3 //打印 // Method java/io/PrintStream.println:(I)V
15: invokestatic #4 //調用方法getN // Method getN:()V
18: return
LineNumberTable: //Java源碼行號和字節碼指令的對應關係
line 8: 0
line 9: 2
line 10: 4
line 11: 8
line 12: 15
line 13: 18
從main方法的描述可知,此方法是一個參數爲String數組,返回值是V的方法,並且此方法是被public修飾ACC_PUBLIC的靜態方法ACC_STATIC.
另外我們還可以知道一個信息:stack=2, locals=4, args_size=1
此方法的棧幀深度爲2,本地變量爲4,參數爲1
由此可見 我們可以認爲:局部變量表:a b c 以及 String數組
程序計數器:0: iconst_1 就是指0 當前操作的位置
操作數棧:深度爲2的棧
動態鏈接: 8: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
返回地址:V
下面來看流程,首先jvm會創建一個線程並重新創建一個線程私有的棧,棧內包含4個局部變量的空間,深度爲2的操作數棧等
然後開始執行code將0賦值給計數器,然後將數字1壓入操作棧,將1賦值給計數器然後將棧頂壓出一位賦給第一個變量a.依次類推,如果當前線程掛起後重新開始運行 則看當前計數器的值 再從當前值得操作往下執行。
再繼續往下直到我們看到
15: invokestatic #4 // Method getN:()V
調用getN()方法,此時會再次創建一個棧幀,流程如上所示,直到方法返回。線程銷燬
另外再來思考另外一個問題。
代碼中static int n = 5; 是何時被初始化的。