Java字節碼詳解(二)字節碼的運行過程

前一章講述了java字節碼文件的生成以及字節碼文件中各個字段代表的含義,在本章節將講述字節碼是什麼運行的

JVM的一些基礎概念

要理解java字節碼的運行情況,首先要了解有關JVM的一些知識,這些是java字節碼運行的先決條件。

JVM數據類型

Java是靜態類型的,它會影響字節碼指令的設計,這樣指令就會期望自己對特定類型的值進行操作。例如,就會有好幾個add指令用於兩個數字相加:iadd、ladd、fadd、dadd。他們期望類型的操作數分別是int、long、float和double。大多數字節碼都有這樣的特性,它具有不同形式的相同功能,這取決於操作數類型。
JVM定義的數據類型包括:

  1. 基本類型:
    • 數值類型: byte (8位), short (16位), int (32位), long (64-bit位), char (16位無符號Unicode), float(32-bit IEEE 754 單精度浮點型), double (64-bit IEEE 754 雙精度浮點型)
    • 布爾類型
    • 指針類型: 指令指針。
  2. 引用類型:
    • 數組
    • 接口

在字節碼中布爾類型的支持是受限的。舉例來說,沒有結構能直接操作布爾值。布爾值被替換轉換成 int 是通過編譯器來進行的,並且最終還是被轉換成 int 結構。Java 開發者應該熟悉所有上面的類型,除了 returnAddress,它沒有等價的編程語言類型。類數組接口在字節碼中布爾類型的支持是受限的。舉例來說,沒有結構能直接操作布爾值。布爾值被替換轉換成 int 是通過編譯器來進行的,並且最終還是被轉換成 int 結構。

Java 開發者應該熟悉所有上面的類型,除了 returnAddress,它沒有等價的編程語言類型。

JVM的內存結構

在這裏插入圖片描述

JVM的內存分佈如上圖所示。方法區和堆是線程共享的,而寄存器、java方法棧、本地方法棧是各個線程私有的。

1.方法區

方法區是用來存儲已被JVM加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
這個區域很少進行垃圾回收,回收目標主要是針對常量池的回收和對類型的卸載。

2.堆

此區域唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存.

3.PC寄存器

程序計數器是一塊較小的內存空間,線程私有。它可以看作是當前線程所執行的字節碼的行號指示器

4. Java方法棧和本地方法棧

JVM棧描述的是java方法執行的內存模型,每個方法在執行的同時都會創建一個棧幀,用於存儲局部變量表、操作數棧、動態鏈接、方法出口等信息.
Java字節碼的運行就是在JVM方法棧中進行的

Java字節碼運行過程

簡單的示例

1.示例源碼

先來看我們的例子代碼,源碼如下:

public class Test{
	public static void main(String[] args){
		Integer a = 1;
		Integer b = 2;
		Integer c = a + b;
	}
}

2.main函數的字節碼展示

使用javac Test.java進行編譯,然後使用javap -v Test.class查看該java文件的字節碼,爲了排除干擾,去除了很多不必要的字節碼

  *** 省略部分字節碼
 Constant pool:
   #1 = Methodref           #5.#14         // java/lang/Object."<init>":()V
   #2 = Methodref           #15.#16        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Methodref           #15.#17        // java/lang/Integer.intValue:()I                                                                                                                                                                                                                                                                                             
  *** 省略部分字節碼
 
  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: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      
        4: astore_1                                                                                          
        5: iconst_2                                                                                          
        6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      
        9: astore_2                                                                                          
       10: aload_1                                                                                           
       11: invokevirtual #3                  // Method java/lang/Integer.intValue:()I                        
       14: aload_2                                                                                           
       15: invokevirtual #3                  // Method java/lang/Integer.intValue:()I                        
       18: iadd                                                                                              
       19: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      
       22: astore_3                                                                                          
       23: return                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             

3.字節碼指令運行過程

接下來分析Code中字節碼運行的過程。這裏說一下,每個指令前的數字爲指令在寄存器中的偏移量。

0: iconst_1 將int常量1進行放入操作數棧。這裏稍微做個拓展,如果將float常量2進行入棧操作,name該指令是fconst_2,詳細的指令種類及意義請查看下一章 Java字節碼指令詳解
在這裏插入圖片描述

1: invokestatic #2 調用常量池中序號爲#2的靜態方法,這裏調用的是 Integer.valueOf()方法,表示將該int類型進行裝箱操作,變爲Integer類型

4: astore_1 在索引爲1的位置將第一個操作數出棧(一個Integer值)並且將其存進本地變量,相當於變量a。
在這裏插入圖片描述

5: iconst_2 將int常量2進行放入操作數棧
在這裏插入圖片描述

6: invokestatic #2 調用常量池中序號爲#2的靜態方法,這裏調用的是 Integer.valueOf()方法,表示將該int類型進行裝箱操作,變爲Integer類型

9: astore_2 在索引爲2的位置將第一個操作數出棧(一個Integer值)並且將其存進本地變量,相當於變量b。
在這裏插入圖片描述

10: aload_1 從索引1的本地變量中加載一個int值,放入操作數棧
在這裏插入圖片描述

11: invokevirtual #3 調用常量池中序號爲#3的實例方法,這裏調用的是 Integer.intValue()方法

14: aload_2 從索引1的本地變量中加載一個int值,放入操作數棧
在這裏插入圖片描述

15: invokevirtual #3 調用常量池中序號爲#3的實例方法,這裏調用的是 Integer.intValue()方法

18: iadd 把操作數棧中的前兩個int值出棧並相加,將相加的結果放入操作數棧。
在這裏插入圖片描述

19: invokestatic #2調用常量池中序號爲#2的靜態方法,這裏調用的是 Integer.valueOf()方法

22: astore_3 在索引爲3的位置將第一個操作數出棧(一個Integer值)並且將其存進本地變量,相當於變量c。
在這裏插入圖片描述

23: return 方法結束

方法調用

上面的示例是比較簡單的,而且只有一個main函數,接下來將展示在多個函數時候字節碼的形式以及運行的具體過程。這裏就直接拿參考文章的示例,原文寫得真的很好,有條件可以去看英文原文。 字節碼的介紹

1.示例源碼

public class Test{
	public static void main(String[] args){
		int a = 1;
		int b = 2;
		int c = calc(1,2);
	}
	
	static int calc(int a,int b){
		return (int) Math.sqrt(Math.pow(a,2)+Math.pow(b,2));
	}
}

2.字節碼展示

  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: istore_1
         2: iconst_2
         3: istore_2
         4: iconst_1
         5: iconst_2
         6: invokestatic  #2                  // Method calc:(II)I
         9: istore_3
        10: return
        
  static int calc(int, int);
    descriptor: (II)I
    flags: ACC_STATIC
    Code:
      stack=6, locals=2, args_size=2
         0: iload_0
         1: i2d
         2: ldc2_w        #3                  // double 2.0d
         5: invokestatic  #5                  // Method java/lang/Math.pow:(DD)D
         8: iload_1
         9: i2d
        10: ldc2_w        #3                  // double 2.0d
        13: invokestatic  #5                  // Method java/lang/Math.pow:(DD)D
        16: dadd
        17: invokestatic  #6                  // Method java/lang/Math.sqrt:(D)D
        20: d2i
        21: ireturn

3. 指令執行過程詳解

上面就是main方法和calc方法的字節碼,由於main方法的指令跟上個例子很相似,唯一不同的是 c=a+b變爲由calc方法去執行並且返回。這裏就不再贅述main方法,接下來主要講解calc方法的執行過程。

0: iload_0 將方法中第一個參數入棧
在這裏插入圖片描述

1: i2d將int類型轉爲double類型

2: ldc2_w #3 將常量池序號爲#3的long型常量從常量池推送至棧頂(寬索引)
在這裏插入圖片描述
5: invokestatic #5 調用靜態方法:Math.pow:() ,並且將結果放入棧頂
在這裏插入圖片描述

8: iload_1
9: i2d
10: ldc2_w #3
13: invokestatic #5
以上的指令跟上一個一樣,進行平方運算
在這裏插入圖片描述

16: dadd 將result和result2相加,並推入棧頂
在這裏插入圖片描述

17: invokestatic #6 調用Math.sqrt()方法

20: d2i 將double類型轉爲int類型

21: ireturn 返回int類型的數值

實例調用

修改上面的代碼,加入對象,並調用對象的方法。

public class Test {
    public static void main(String[] args){

	   Point a  =new Point (1,2);
	   Point b = new Point (3,4);
	   int c = a.area(b);
    }

    static class Point{
        private int x;
        private int y;

        public Point(int x,int y){
            this.x = x;
            this.y = y;
        }
        
        public int  area(Point p){
            int length  = Math.abs(p.y-this.y);
            int width  = Math.abs(p.x-this.x);
            return length*width;
        }
    }
}

使用javap -v Test查看編譯後的字節碼:

  public static void main(java.lang.String[]);                                                    
    descriptor: ([Ljava/lang/String;)V                                                            
    flags: ACC_PUBLIC, ACC_STATIC                                                                 
    Code:                                                                                         
      stack=4, locals=4, args_size=1                                                              
         0: new           #2                  // class Test3$Point                                
         3: dup                                                                                   
         4: iconst_1                                                                              
         5: iconst_2                                                                              
         6: invokespecial #3                  // Method Test3$Point."<init>":(II)V                
         9: astore_1                                                                              
        10: new           #2                  // class Test3$Point                                
        13: dup                                                                                   
        14: iconst_3                                                                              
        15: iconst_4                                                                              
        16: invokespecial #3                  // Method Test3$Point."<init>":(II)V                
        19: astore_2                                                                              
        20: aload_1                                                                               
        21: aload_2                                                                               
        22: invokevirtual #4                  // Method Test3$Point.area:(LTest3$Point;)I         
        25: istore_3                                                                              
        26: return                                                                                

這個main方法比上一個例子多了幾個新的指令:new,dup,invokespecial

  • new new 指令與編程語言中的 new 運算符類似,它根據傳入的操作數所指定類型來創建對象(這是對 Point 類的符號引用)。

  • dup dup指令會複製頂部操作數的棧值,這意味着現在我們在棧頂部有兩個指向Point對象的引用。
    在這裏插入圖片描述

  • iconst_1iconst_2invokespecial,將x,y的值(1,2)壓入棧頂,接下來進行Point初始化工作,將x,y的值進行賦值。初始化完成後會將棧頂的三個操作引用銷燬,只留下最初的Point的對象引用。
    在這裏插入圖片描述

  • astore_1 將該Point引用出棧,並將其賦值到索引1所保存的本地變量(astore_1中的a表明這是一個引用值)
    在這裏插入圖片描述

接下來進行第二個Point實例的初始化和賦值操作
在這裏插入圖片描述

  • 20: aload_1,21: aload_2 將a,b的Point實例的引用入棧
  • 22: invokevirtual #4 調用 area方法,
  • 25: istore_3 將返回值放入索引3中(即賦值給c)
  • return 方法結束

總結

本章節寫了字節碼運行的詳細過程,詳細的指令介紹在下一章,有興趣可以看看。


參考文章:
字節碼的介紹

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