JAVA是如何運行的二

綜上篇介紹,下面來詳細講解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; 是何時被初始化的。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章