Java字節碼指令分析

文章已同步github博客:Java字節碼指令分析

1、概念

分析字節碼指令之前,先明確以下幾個概念;

1.1、程序計數器

​JVM中的程序計數器,執行非native方式時,程序計數器保存Java虛擬機正在執行的字節碼指令地址,對於native方法,保存的值是undefined;

1.2、虛擬機棧

​線程私有,用於存儲棧幀,只收棧幀出棧和入棧影響,活動線程中,只有位於棧頂的棧幀纔是有效的,稱爲當前棧幀,與這個棧幀相關聯的方法稱爲當前方法;

1.3、棧幀

​棧幀(Stack Frame)是用於支持虛擬機進行方法調用和方法執行的數據結構,是虛擬機運行時數據區中的虛擬機棧的棧元素,棧幀存儲了方法的局部變量表、操作數棧、動態連接和方法返回地址(以及附加信息)等信息,每一個方法從調用至執行完成的過程,都對應着一個棧幀在虛擬機棧裏從入棧到出棧的過程,棧幀隨方法的調用而創建,隨方法的結束而銷燬(無論方法正常結束還是異常退出都算結束);

​每一個棧幀都有自己的本地變量表、操作數棧、指向當前方法所屬的類的運行時常量池的引用;

1.3.1、局部變量表

​每個棧幀中包含一個局部變量表,長度由編譯期決定,並且存儲於類或接口的二進制表中,即通過方法的code屬性保存及提供給棧幀使用;

​局部變量表的存儲單位爲Slot,對於不超過32位的數據類型佔用1個單位的Slot,64位類型的則佔2個(long類型、double類型),虛擬機通過索引定位的方式使用局部變量表,索引值的範圍是從0到局部變量表最大的Slot數量;

​執行非static方法時,局部變量表中索引爲0的Slot默認是用於傳遞方法所屬對象示例的引用,在方法中可以通過關鍵字“this”來訪問這個隱含的參數;

​局部變量表中的Slot是可重用的,當程序計數器的值已經超過某個變量的作用域時,這個變量對應的Slot可以交給其他變量使用;

1.3.2、操作數棧

​每個棧幀內都有一個操作數棧,後進先出,其最大深度由編譯器決定,並且通過放的code屬性保存及提供給棧使用;

​操作數棧中的每個位置可以保存一個Java虛擬機中定義的任意數據類型的值,並且在任意時刻操作數棧都有一個確定的棧深度,一個long或者double類型的數據會佔用兩個單位的棧深度,其他數據類型則會佔用一個單位的棧深度;

1.3.3、動態鏈接

​每個戰爭內部都包含一個指向當年情方法所在類型的運行時常量池的引用,以便對當前方法的代碼實現動態鏈接,在class文件裏面,一個方法若要調用其他方法或者訪問成員變量,則需要通過符號引用來表示,動態鏈接的作用就是將這些以符號引用所表示的的方法轉換爲對實際方法的直接引用,類加載的過程中將要解析尚未被解析的符號引用,並且將對變量的訪問變量的訪問轉化爲變量在程序運行時,位於存儲結構中的正確偏移量。

1.4、指令

​Java虛擬機的指令由一個字節長度的、代表某種特定操作含義的操作碼,以及跟隨其後的零至多個代表此操作所需參數的操作數所構成,許多指令並不包含操作數,只有一個操作碼;

2、方法解析

​在解析的階段,如果方法在程序真運行之前就有一個可確定的調用版本,並且這個方法的調用版本在運行期是不可改變的,在類加載的解析階段,會將其中的一部分符號引用轉化爲直接引用,在Java中符合這種“編譯期可知,運行期不可變”的方法,主要包括靜態方法和私有方法兩大類,他們都不可能通過繼承或別的方式重寫其他版本所以都適合在類加載階段進行解析;

驗證:

2.1、公有方法編譯

java文件:

public class Test {
  
  public void main(String[] args) {
    int i = this.addTest();
  }
  
  public int addTest() {
    int a1 = 200;
    int a2 = 404;
    int a3 = 500;
    return a1 + a2 + a3;
  }
}

彙編代碼:

xxxdeMacBook-Pro:xxx xxx$ javap -v Test.class 
Classfile /Users/xxx/xxx/test/Test.class
  Last modified 2019-12-12; size 371 bytes
  MD5 checksum 6fd0f3f4b59fe92defcba034fd98f2f9
  Compiled from "Test.java"
public class com.jesus.util.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Methodref          #3.#16         // com/jesus/util/Test.addTest:()I
   #3 = Class              #17            // com/jesus/util/Test
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               addTest
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java
  #15 = NameAndType        #5:#6          // "<init>":()V
  #16 = NameAndType        #11:#12        // addTest:()I
  #17 = Utf8               com/jesus/util/Test
  #18 = Utf8               java/lang/Object
{
  public com.jesus.util.Test();
    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 3: 0

  public void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=2
         0: aload_0
         1: invokevirtual #2                  // Method addTest:()I
         4: istore_2
         5: return
      LineNumberTable:
        line 6: 0
        line 7: 5

  public int addTest();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: sipush        200
         3: istore_1
         4: sipush        404
         7: istore_2
         8: sipush        500
        11: istore_3
        12: iload_1
        13: iload_2
        14: iadd
        15: iload_3
        16: iadd
        17: ireturn
      LineNumberTable:
        line 10: 0
        line 11: 4
        line 12: 8
        line 14: 12
}
SourceFile: "Test.java"

2.2、私有方法編譯

java文件:

public class Test {
  
  public void main(String[] args) {
    int i = this.addTest();
  }
  
  private int addTest() {
    int a1 = 200;
    int a2 = 404;
    int a3 = 500;
    return a1 + a2 + a3;
  }
}

彙編代碼:

xxxdeMacBook-Pro:xxx xxx$ javap -v Test.class 
Classfile /Users/xxx/xxx/test/Test.class
  Last modified 2019-12-12; size 371 bytes
  MD5 checksum 95f972b8e292084a943c51acb9c88dad
  Compiled from "Test.java"
public class com.jesus.util.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Methodref          #3.#16         // com/jesus/util/Test.addTest:()I
   #3 = Class              #17            // com/jesus/util/Test
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               addTest
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               Test.java
  #15 = NameAndType        #5:#6          // "<init>":()V
  #16 = NameAndType        #11:#12        // addTest:()I
  #17 = Utf8               com/jesus/util/Test
  #18 = Utf8               java/lang/Object
{
  public com.jesus.util.Test();
    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 3: 0

  public void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=2
         0: aload_0
         1: invokespecial #2                  // Method addTest:()I
         4: istore_2
         5: return
      LineNumberTable:
        line 6: 0
        line 7: 5
}
SourceFile: "Test.java"

3、編譯代碼

3.1、Java文件

public class Demo {
  
  public String sayHello() {
    String str1 = "Hello";
    String str2 = "World";
    String str3 = "!";
    return str1 + str2 + str3;
  } 
}

3.2、指令

使用命令javac Demo.java將其編譯爲.class文件,再通過javap -c Demo.class命令來打印出該類的指令集,關於javap一般用到以下幾個命令:

javap -c xxx.class  // 反編譯生成彙編代碼
javap -l xxx.class  // 輸出行號和本地變量表信息
javap -v xxx.class  // 不僅會輸出行號、本地變量表信息、反編譯彙編代碼,還會輸出當前類用到的常量池等信息

彙編代碼:

xxxdeMacBook-Pro:xxx xxx$ javap -v Demo.class 
Classfile /Users/xxx/xxx/test/Demo.class
  Last modified 2019-12-12; size 464 bytes
  MD5 checksum 4a27abd7c7fc5b1016fb72453de95fa7
  Compiled from "Demo.java"
public class com.xxx.xxx.Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#19        // java/lang/Object."<init>":()V
   #2 = String             #20            // Hello
   #3 = String             #21            // World
   #4 = String             #22            // !
   #5 = Class              #23            // java/lang/StringBuilder
   #6 = Methodref          #5.#19         // java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#24         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#25         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Class              #26            // com/jesus/util/Demo
  #10 = Class              #27            // java/lang/Object
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               sayHello
  #16 = Utf8               ()Ljava/lang/String;
  #17 = Utf8               SourceFile
  #18 = Utf8               Demo.java
  #19 = NameAndType        #11:#12        // "<init>":()V
  #20 = Utf8               Hello
  #21 = Utf8               World
  #22 = Utf8               !
  #23 = Utf8               java/lang/StringBuilder
  #24 = NameAndType        #28:#29        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #25 = NameAndType        #30:#16        // toString:()Ljava/lang/String;
  #26 = Utf8               com/jesus/util/Demo
  #27 = Utf8               java/lang/Object
  #28 = Utf8               append
  #29 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #30 = Utf8               toString
{
  public com.jesus.util.Demo();
    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 3: 0

  public java.lang.String sayHello();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: ldc           #2                  // String Hello
         2: astore_1
         3: ldc           #3                  // String World
         5: astore_2
         6: ldc           #4                  // String !
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: aload_3
        25: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        28: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        31: areturn
      LineNumberTable:
        line 6: 0
        line 7: 3
        line 8: 6
        line 10: 9
}
SourceFile: "Demo.java"

4、指令解析

sayHello方法字節碼文件:

0x0001 000f 0010 0001 000d 0000 0044 0002 0004 0000 0020 1202 4c12 034d 1204 4ebb 0005 59b7 0006 2bb6 0007 2cb6 0007 2db6 0007 b600 08b0 0000 0001 000e 0000 0012 0004 0000 0006 0003 0007 0006 0008 0009 000a

sayHello方法字節碼指令:

Code:
	stack=2, locals=4, args_size=1
    0: ldc           #2                  // String Hello
    2: astore_1
    3: ldc           #3                  // String World
    5: astore_2
    6: ldc           #4                  // String !
    8: astore_3
    9: new           #5                  // class java/lang/StringBuilder
    12: dup
    13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
    16: aload_1
    17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    20: aload_2
    21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    24: aload_3
    25: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
    28: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
    31: areturn

4.1、基於棧的解釋器執行

4.1.1、初始狀態

​當執行該構造方法時,當前線程的java虛擬機棧中入棧一個棧幀,代表該構造方法的棧幀,剛開始棧幀中的操作數棧爲空,局部變量表中存儲隱含的this參數,此時程序計數器記錄的執行地址是0,如下圖:

在這裏插入圖片描述

4.1.2、ldc

​idc,查詢《深入理解Java虛擬機·附錄B 虛擬機字節碼指令表》可知,該指令對應的操作碼爲“0x12”,表示將int、float或String類型常量值從常量池中推送至棧頂,該指令操作後面跟一個字節的參數,對應class文件中的值爲“0x12 02”,02爲參數,指向常量池中索引爲2的常量,即“Hello”,所以執行該指令就是將常量池中的常量“Hello”,壓入操作數棧的棧頂,此時程序計數器記錄地址偏移爲0,如下圖:
在這裏插入圖片描述

4.1.3、astore_1

​查詢指令表可知,該指令對應的操作碼爲“0x4c”,表示將棧頂引用類型數值存入第二個局部變量,因爲此時局部變量表中第一個值爲默認的隱含參數“this”,所以將操作數中的“Hello”出棧,並存儲爲局部變量表的第二個變量,此時程序計數器記錄的地址偏移爲2,如下圖:
在這裏插入圖片描述

4.1.4、Idc、astore_2、Idc、astore_3

​此步驟操作循環上面兩步,略,完成後結果如下圖:
在這裏插入圖片描述

4.1.5、new

​new指令對應的操作碼爲“0xbb”,意爲創建一個對象,並將其引用值壓入棧頂,new指令後面跟着一個u2類型的2個字節的參數,即“0x0005”,指向常量池中索引爲5的常量,是一個Class對象,查看後面引用可知爲StringBuilder對象,此時聲明瞭一個StringBuilder對象,並將其引用壓入操作數棧,需要注意的是,此處只是聲明瞭對象,並沒有執行到具體初始化該對象的方法,此時程序計數器記錄的地址偏移爲9,如下圖:
在這裏插入圖片描述

4.1.6、dup

​該指令對應的操作碼爲“0x59”,意爲複製棧頂數值並將複製的值壓入棧頂,即將棧頂StringBuilder對象引用出棧,並複製,將複製的值重新壓入操作數棧頂,此時程序計數器記錄操作地址偏移爲12,如下圖:
在這裏插入圖片描述

4.1.7、invokespecial

​該指令的操作碼爲“0xb7”,意爲調用超類構造方法、實例化初始化方法、私有方法,即真正意義上的實例化StringBuilder對象,此時棧頂的stringBuilder對象引用出棧,調用其實例化方法,並將執行結果返回到該處,該指令後面跟一個u2類型的2字節參數,“0x0006”,指向常量池中索引爲6的常量,即“java/lang/StringBuilder.""😦)V”,StringBuilder的實例化方法,此時程序計數器記錄執行地址偏移爲13,如下圖:
在這裏插入圖片描述
執行到此處,由於調用StringBuilder的構造函數去創建StringBuilder對象,所以此時保存該棧幀的信息,新的棧幀入棧,即Java虛擬機棧中入棧表示執行StringBuilder構造方法的棧幀,爲當前棧幀,執行完畢之後,該棧幀出棧,將創建的StringBuilder對象返回到該方法的調用處,即sayHello()方法的棧幀,此時該棧幀恢復,程序計數器中記錄的當前棧幀的操作指令執行的地址偏移,所以可以恢復到該處繼續執行;

4.1.8、aload_1

​對應的操作碼爲“0x2b”,即將第二個引用類型的本地變量推送至棧頂,即將佈局變量表中的第二個變量,索引爲1的變量,也就是“Hello”,壓入棧頂,此時程序計數器記錄的指令操作地址偏移量爲16,如下圖:
在這裏插入圖片描述

4.1.9、invokevirtual

​對應的操作碼爲“0xb6”,意爲調用實例方法,該指令後面跟一個u2類型的2字節參數,即“0x0007”,指向常量池中索引爲7的常量,值爲“java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;”,也就是StringBuilder的append()方法,此時執行該方法,將“Hello”值賦值給stringBuilder對象,返回,該處也是新棧幀入棧,執行完畢出棧,回到sayHello()方法的棧幀繼續執行,記錄返回值,此時程序計數器記錄的地址偏移爲17,如下圖:
在這裏插入圖片描述

4.1.10、aload_2、invokevirtual、aload_3、invokevirtual

​此處執行將剩餘兩個值“World”和“!”,與前面的“Hello”拼接,並保存到stringBuilder對象中,此時執行完程序計數器對應的地址偏移爲25,如下圖:
在這裏插入圖片描述

4.1.11、invokevirtual

​對應的操作碼爲“0xb6”,意爲調用實例方法,該指令後面跟一個u2類型的2字節參數,即“0x0008”,指向常量池中索引爲8的常量,值爲“java/lang/StringBuilder.toString:()Ljava/lang/String;”,也就是StringBuilder的toString()方法,此時執行該方法,將StringBuilder對象轉爲字符串,返回,該處也是新棧幀入棧,執行完畢出棧,回到sayHello()方法的棧幀繼續執行,記錄返回值,此時程序計數器記錄的地址偏移爲28,如下圖:
在這裏插入圖片描述

4.1.12、areturn

​對應的操作碼爲“0xb0”,意爲從當前方法返回對象引用,即將當前操作數棧定的stringBuilder對象出棧,並返回,此時操作數棧已經清空,此時程序計數器記錄執行地址偏移爲31,如下圖:
在這裏插入圖片描述
此時已經執行到return指令意爲該方法執行結束,此時該棧幀從Java虛擬機棧中出棧,此時局部變量也已經銷燬,所以在方法執行完之後,局部變量就沒有用了,若此時發生GC,這些對象將被回收;

​通過以上執行指令分析,也可知道在Java代碼中用“+”號將字符串拼接,在編譯時會使用StringBuilder的append()方法執行;

​以上分析完可知,字節碼反編譯的彙編代碼,Code屬性下面地址偏移的規律,從0位置開始,如果執行是後面帶參數,參數所佔的位置也會使地址偏移,例如“ldc”指令後面跟一個參數,其地址偏移一位,上面分析的第一步,所以第二步的“astore_1”指令的地址偏移爲2,其中地址偏移爲1的被參數佔用,後面同理;

5、手動java到指令代碼

Java代碼

public class Test {
  
  public int addTest() {
    int a1 = 20;
    int a2 = 404;
    
    return a1 + a2;
  } 
}

​分析該方法,該方法訪問權限爲public,無參數(默認隱含一個this參數,存入局部變量表),返回值爲int類型,該方法的步驟應該如下:

  • 首先應該是分別將兩個int類型的值存入到局部變量表中,即從操作數棧出棧,存入局部變量表;
  • 然後從局部變量表中分別取出兩個變量;
  • 執行相加操作;
  • 執行返回;
// 20屬於單字節常量,將其壓入操作數棧頂,後面跟上20的值
bipush 20
// 將操作數棧頂出棧,存儲到局部變量中第二個位置,第一個位置爲this
istore_1
// 404屬於短整型常量,將其壓入操作數棧頂,後面跟上404的值
sipush 404
// 將操作數棧頂出棧,存儲到局部變量中第三個位置
istore_1
// 加載局部變量表中的第二個值到操作數棧頂,即20
iload_1
// 加載局部變量表中的第三個值到操作數棧頂,即404
iload_2
// 執行相加操作
iadd
// 將操作的值返回
ireturn

​以上皆爲查詢《深入理解Java虛擬機·附錄B 虛擬機字節碼指令表》對照相應的操作指令,然後將Java文件編譯爲class文件,執行命令javap -v打印出該類的指令代碼,然後對比分析,本方法用了兩個局部變量,加上this,一共需要三個局部變量位置,所以局部變量表大小爲3,執行過程中,最大棧深度爲2,即將兩個變量都加載到操作數棧時,也就是執行相加的前一步;

字節碼指令:

Code:
	stack=2, locals=3, args_size=1
    0: bipush        20
    2: istore_1
    3: sipush        404
    6: istore_2
    7: iload_1
    8: iload_2
    9: iadd
    10: ireturn

6、手動Java到字節碼

​以上方法,對照之前寫過的class文件解析,將其手動翻譯爲字節碼文件,以下是需要用到的class文件分析方法時的表結構;

方法表結構:

{
    u2             access_flags; // 訪問限定符
    u2             name_index; // 名稱索引
    u2             descripotr_index; // 描述符索引
    u2             attributes_count; // 屬性數量
    attribute_info attributes; // 屬性表集合
}

Code代碼屬性表結構:

{
    u2             attribute_name_index; // 固定爲”Code“,表示屬性名稱
    u4             attribute_length; // 該屬性值的長度
    u2             max_stack; // 操作數棧深度的最大值,根據該值來分配棧幀中的操作站深度
    u2             max_locals; // 局部變量表所需的存儲空間
    u4             code_length; // 字節碼長度
    u1             code; // 存儲java源程序編譯後的字節碼指令
    u2             exception_table_length; //
    exception_info exception_table; //
    u2             attribute_count; //
    attribute_info attributes; //
}

手動翻譯:

// 方法修飾符public,u2類型,只有public屬性,查詢PUBLIC對應的十六進制碼
0x0001
// 接下來方法名索引和方法描述索引都爲u2類型,需要對照常量池,所以這裏用a佔位
0xaaaa aaaa
// u2類型的屬性數量暫時不統計,用b佔位
0xbbbb
// 接着分析方法表屬性,Code屬性名固定u2類型,值也爲固定,但需要對應常量池中索引,所以用c佔位
0xcccc
// 屬性值長度,固定u4類型,暫不統計,d佔位
0xdddd dddd
// 接下來分別是兩個u2類型的棧深度和本地變量表大小,有上面可知棧深度爲2,局部變量表大小爲3
0x0002 0003
// u4類型的字節碼長度,暫不統計,e佔位
0xeeee eeee
// 接着是分別分析u1類型的code指令,對照指令表查詢操作碼
// bipush操作碼爲0x10,後面跟單字節值20,十六進制爲0x14
0x1014
// istore_1操作碼爲0x3c
0x3c
// sipush操作碼爲0x11,後面跟一個短整型參數404,需要兩個字節表示,單字節-128~127,即0x0194
0x11 0194
// istore_2操作碼爲0x3d
0x3d
// 接下來兩個iload_1、iload_2分別操作碼爲0x1b、0x1c
0x1b1c
// iadd相加,操作碼爲0x60
0x60
// 返回ireturn對應操作碼爲0xac
0xac
// 無try catch,無拋出異常,u2類型異常長度爲0
0x0000


// 後面省略...所以最終的字節碼分析爲:
0x0001 aaaa aaaa bbbb cccc dddd dddd 0002 0003 eeee eeee 1014 3c11 0194 3d1b 1c60 ac00 00...

​使用命令javac編譯該java代碼,打開字節碼指令,按class文件類結構找到該方法對應的所有字節碼,如下:

class文件:

0x0001 0008 0009 0001 0006 0000 002b 0002 0003 0000 000b 1014 3c11 0194 3d1b 1c60 ac00 0000 0100 0700 0000 0e00 0300 0000 0600 0300 0700 0700 09

對比可知,常量池中的索引無法確定,方法的字節碼指令是一致的,即“0x1014 3c11 0194 3d1b 1c60 ac”;

參考書籍:

​ 《深入理解Java虛擬機》

​ 《Java虛擬機規範 Java SE 8版》

發佈了32 篇原創文章 · 獲贊 14 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章