Java的JVM字節碼指令集詳解

  本文詳細介紹瞭如何使用javap查看java方法中的字節碼、以及各種字節碼的含義,並且配以完善的案例,一步步,從頭到尾帶領大家翻譯javap的輸出。在文末還附有JVM字節碼指令集表。
  本文不適合沒有JVM基礎的初學者,看文章之前希望你有如下基本技能:瞭解JVM的一些基本概念,比如什麼是符號引用?什麼是字面量?瞭解class文件的基本結構,瞭解基於棧的JVM方法執行的棧基本結構!關於class文件的結構可以看:Java的 Class(類)文件結構詳解
  看完本文,你可能獲得如下知識:JVM字節碼的入門、如何查看和分析字節碼的執行、加深對JVM中的棧的理解,基於字節碼層面,或許還能爲你解開一些原始的疑惑,比如:爲什麼finally塊中的語句總會被執行?

1 字節碼概述

  字節碼是一套設計用來在Java虛擬機中執行的高度優化的指令集。Java字節碼對於虛擬機,就好像彙編語言對於計算機,屬於基本執行指令,或者說是Java代碼的在JVM中的最小執行單元。JVM只關心字節碼,而不關心源文件是屬於在哪個平臺用哪種語言編寫的,字節碼是實現JVM平臺無關性和語言無關性的基石。
  JVM字節碼指令由一個字節長度的、代表着某種特定操作含義的數字稱爲操作碼,Opcode),以及跟隨其後的零到多個代表此操作所需參數(稱爲操作數,Operands)構成。
  所謂一個字節的長度,也就是8位二進制數字,也就是兩位十六進制數字。
  每一個字節碼還擁有它對應的助記符形式。顧名思義,助記符就是幫助我們查看、理解字節碼的含義的符號,一般我們看字節碼操作都通過是看助記符來分辨的。但是實際的執行運行並不存在助記符這些東西,都是根據字節碼的值來執行。
  由於操作碼的長度爲一個字節,因此操作碼(指令)最多不超過256條;
  對於操作數長度超過了一個字節的情況(取決於操作碼):由於Class文件格式放棄了編譯後代碼的操作數長度對齊,所以,當JVM處理超過一個字節長度的數據時,需要在運行時從字節中重建出具體數據的結構,如16位二進制數據需要(byte1 << 8)|byte2操作(Big-Endian 順序存儲——即高位在前的字節序)。

2 基於javap的字節碼解析

2.1 javap介紹

  如果直接打開class文件,只能查看數字形式的字節碼。
  我們可以使用javap工具,反編譯class文件,將數字類型的字節碼轉換成我們能看懂的格式,進而快捷的查看一個java類的常量池、方法表、code屬性(方法字節碼)、局部變量表、異常表等等信息。在這裏,我認爲javap反編譯之後輸出的數據,並不是傳統意義上的彙編指令,只是The Java Virtual Machine Instruction Set(Java 虛擬機指令集)的一種方便人們理解的表現形式。

javap的格式爲: javap < options > < classes >

其中options選項如下:

-version 版本信息,其實是當前javap所在jdk的版本信息,不是cass在哪個jdk下生成的。
-l 輸出行號和本地變量表
-c 對代碼進行反彙編,主要是針對方法的代碼
-v -verbose 不僅會輸出行號、本地變量表信息、反編譯彙編代碼,還會輸出當前類用到的常量池等詳細信息。
-pubic 僅顯示公共類和成員
-protected 顯示受保護的/公共類和成員
-package 顯示程序包/受保護的/公共類 和成員 (默認)
-p -private 顯示所有類和成員
-s 輸出內部類型簽名
-sysinfo 顯示正在處理的類的系統信息 (路徑, 大小, 日期, MD5 散列)
-constants 顯示靜態最終常量
-classpath 指定查找用戶類文件的位置
-bootclasspath 覆蓋引導類文件的位置

  一般常用的是-v -l -c三個選項,-v展示的最全面。官方javap介紹地址爲:javap

2.2 javap案例

2.2.1 源碼類

  源碼類使用在介紹class文件結構時候使用的源碼類:Java的 Class(類)文件結構詳解。方便連續學習。這裏再貼出來一下:

public class ClassFile {
    public static final String J = "2222222";

    private int k;

    public int getK() {
        return k;
    }

    public void setK(int k) throws Exception {

        try {
            this.k = k;
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } finally {
        }
    }

    public static void main(String[] args) {
    }
}

2.2.2 javap輸出解析

  對源碼的class文件,使用javap -v ClassFile.class指令,得到如下信息(灰色的文字是後來自己加的註釋)。
  對於初學者,應該一行行的仔細查看其含義,對於重要的部分單獨拿出來講解,比如方法字節碼部分。

Classfile /J:/Idea/jvm/target/classes/com/ikang/JVM/classfile/ClassFile.class  //生成該class文件的源文件名稱和路徑
  Last modified 2020-4-8; size 960 bytes  //上一次修改時間和大小
  MD5 checksum fcd8ef238722b6dab50c0495a9673a1f  //MD5信息
  Compiled from "ClassFile.java"  //編譯自ClassFile.java類
public class com.ikang.JVM.classfile.ClassFile
  minor version: 0   //小版本號
  major version: 52  //大版本號
  flags: ACC_PUBLIC, ACC_SUPER  //類訪問標記, ACC_PUBLIC表示public, ACC_SUPER表示允許使用invokespecial字節碼指令的新語意

//class常量池開始,可以看出有46個常量
Constant pool:
   //第1個常量是CONSTANT_Methodref_info類型,表示類中方法的符號引用,具有兩個索引屬性,分別指向第6個和第37個常量,其最終值爲java/lang/Object."<init>":()V
  //這明顯是< init >方法 的符號引用,編譯時自動生成的。
   #1 = Methodref          #6.#37         // java/lang/Object."<init>":()V
   //第2個常量是CONSTANT_Fieldref_info類型,表示類中字段的符號引用,具有兩個索引屬性,分別指向第5個和第38個常量  其最終值爲com/ikang/JVM/classfile/ClassFile.k:I
  //這明顯是k字段的符號引用
   #2 = Fieldref           #5.#38         // com/ikang/JVM/classfile/ClassFile.k:I
   //第3個常量是CONSTANT_Class_info類型,表示類或接口的符號引用,具有一個索引屬性,指向第39個常量  其最終值爲java/lang/IllegalStateException
   //這明顯是引入的IllegalStateException異常類的符號引用
   #3 = Class              #39            // java/lang/IllegalStateException
   //第4個常量是CONSTANT_Methodref_info類型,表示類中方法的符號引用,具有兩個索引屬性,分別指向第3個和第40個常量,其最終值爲java/lang/IllegalStateException.printStackTrace:()V
   //這明顯是e.printStackTrace()方法的符號引用
   #4 = Methodref          #3.#40         // java/lang/IllegalStateException.printStackTrace:()V
   //第5個常量是CONSTANT_Class_info類型,表示類或接口的符號引用,具有一個索引屬性,指向第41個常量  其最終值爲com/ikang/JVM/classfile/ClassFile
   //這明顯是本類(ClassFile類)的符號引用
   #5 = Class              #41            // com/ikang/JVM/classfile/ClassFile
   //第6個常量是CONSTANT_Class_info類型,表示類或接口的符號引用,具有一個索引屬性,指向第41個常量  其最終值爲java/lang/Object
   //這明顯是本類的父類Object的符號引用
   #6 = Class              #42            // java/lang/Object
   //第7個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是J
   //這明顯儲存的常量J的名稱的字面值
   #7 = Utf8               J
   //第7個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是Ljava/lang/String;
   //這明顯表示一個對象類型的字段描述符的字面值,String類型
   #8 = Utf8               Ljava/lang/String;
   //第9個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是ConstantValue
   //這明顯表示常量字段的ConstantValue屬性的名字的字符串字面值
   #9 = Utf8               ConstantValue
   //第10個常量是CONSTANT_String_info類型,表示字符串類型字面量,用來表示類型,具有一個指向具體字面值的索引43, 這裏的具體值是2222222
   //這明顯是常量 J的字面量,因爲是字符串類型
  #10 = String             #43            // 2222222
   //第11個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 k
   //這明顯儲存的變量J的名稱的字面值
  #11 = Utf8               k
   //第12個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 I
   //這明顯表示一個int類型的字段描述符的字面值
  #12 = Utf8               I
   //第13個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 <init>
   //這明顯表示<init>方法名稱的字面值
  #13 = Utf8               <init>
   //第14個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 ()V
   //這明顯表示一個返回值爲void類型、沒有參數的方法描述符的字面值,那麼可以表示構造方法的方法描述符的字面值
  #14 = Utf8               ()V
   //第15個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 Code
   //這明顯表示方法中的Code屬性的名稱的字面值
  #15 = Utf8               Code
   //第16個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 k
   //這明顯表示方法的Code屬性中的LineNumberTable屬性的名稱的字面值
  #16 = Utf8               LineNumberTable
   //第17個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 k
   //這明顯表示方法的Code屬性中的LocalVariableTable屬性的名稱的字面值
  #17 = Utf8               LocalVariableTable
   //第18個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 this
   //這明顯表示方法的局部變量this的名稱的字面值
  #18 = Utf8               this
   //第19個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 Lcom/ikang/JVM/classfile/ClassFile;
   //這明顯表示一個對象類型的字段描述符的字面值,ClassFile類型
  #19 = Utf8               Lcom/ikang/JVM/classfile/ClassFile;
   //第20個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 getK
   //這明顯表示getK方法名稱的字面值
  #20 = Utf8               getK
   //第21個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 ()I
   //這明顯表示一個返回值爲int類型、沒有參數的方法描述符的字面值,那麼可以表示getK方法的方法描述符的字面值
  #21 = Utf8               ()I
   //第22個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 setK
   //這明顯表示setK方法名稱的字面值
  #22 = Utf8               setK
   //第23個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 (I)V
   //這明顯表示一個返回值爲void類型、參數爲int類型的方法描述符的字面值,那麼可以表示setK方法的方法描述符的字面值
  #23 = Utf8               (I)V
   //第24個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 e
   //這個e是什麼呢?實際上是表示參數的名稱的字面值 
  #24 = Utf8               e
   //第25個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是Ljava/lang/IllegalStateException;
   //這明顯表示一個對象類型的字段描述符的字面值,IllegalStateException類型
  #25 = Utf8               Ljava/lang/IllegalStateException;
   //第26個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是Ljava/lang/IllegalStateException;
   //這明顯表示方法的Code屬性中的StackMapTable屬性的名稱的字面值
  #26 = Utf8               StackMapTable
   //第27個常量是CONSTANT_Class_info類型,表示類或接口的符號引用,具有一個索引屬性,指向第39個常量  其最終值爲java/lang/IllegalStateException
   //這明顯是IllegalStateException類的符號引用
  #27 = Class              #39            // java/lang/IllegalStateException
   //第28個常量是CONSTANT_Class_info類型,表示類或接口的符號引用,具有一個索引屬性,指向第44個常量  其最終值爲java/lang/Throwable
   //這明顯是Exception的父類Throwable類的符號引用
  #28 = Class              #44            // java/lang/Throwable
   //第29個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是Exceptions
   //加了個s,肯定不是類名字面量了,實際上它是方法表中的Exceptions異常表屬性的名字 字符串字面量
  #29 = Utf8               Exceptions
   //第30個常量是CONSTANT_Class_info類型,表示類或接口的符號引用,具有一個索引屬性,指向第45個常量  其最終值爲java/lang/Exception
   //這明顯是Exception類的符號引用
  #30 = Class              #45            // java/lang/Exception
   //第31個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是main
   //這明顯是main方法的方法名字 字符串字面量
  #31 = Utf8               main
   //第32個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 ([Ljava/lang/String;)V
   //這明顯表示一個返回值爲void類型、參數爲String一維數組類型的方法描述符的字面值,那麼可以表示main方法的方法描述符的字面值
  #32 = Utf8               ([Ljava/lang/String;)V
   //第33個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 ([Ljava/lang/String;)V
   //這明顯表示main方法的參數名的字符串字面量
  #33 = Utf8               args
   //第34個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 [Ljava/lang/String;
   //這明顯表示一個對象類型的字段描述符的字面值,String[]類型
  #34 = Utf8               [Ljava/lang/String;
   //第35個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 SourceFile
   //這明顯表示SourceFile屬性的名字的字面值
  #35 = Utf8               SourceFile
   //第36個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 ClassFile.java
   //這明顯表示SourceFile屬性的具體值的字面值
  #36 = Utf8               ClassFile.java
   //第37個常量是CONSTANT_NameAndType_info類型,表示字段或方法的部分符號引用,還持有兩個索引,分別指向13和14個字符串,這裏的最終值是 "<init>":()V,我們回過頭去看第13和14個字符串,它們儲存的是具體的字面值。
  //這個常量被我們的第1個常量持有,即表示< init >方法的部分符號引用
  #37 = NameAndType        #13:#14        // "<init>":()V
   //第38個常量是CONSTANT_NameAndType_info類型,表示字段或方法的部分符號引用,還持有兩個索引,分別指向11和12個字符串,這裏的最終值是 k:I,我們回過頭去看第11和12個字符串,它們儲存的是具體的字面值。
  //這個常量被我們的第2個常量持有,即表示k字段的部分符號引用
  #38 = NameAndType        #11:#12        // k:I
   //第39個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 java/lang/IllegalStateException
   //這個常量被我們的第3、27個常量持有,這明顯表示儲存IllegalStateException類的符號引用的具體字面值
  #39 = Utf8               java/lang/IllegalStateException
   //第40個常量是CONSTANT_NameAndType_info類型,表示字段或方法的部分符號引用,還持有兩個索引,分別指向46和14個字符串,這裏的最終值是 k:I,我們去看第46和14個字符串,它們儲存的是具體的字面值。
  //這個常量被我們的第4個常量持有,即表示printStackTrace方法的部分符號引用
  #40 = NameAndType        #46:#14        // printStackTrace:()V
   //第41個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 com/ikang/JVM/classfile/ClassFile
   //這個常量被我們的第5個常量持有,這明顯表示儲存本類(ClassFile類)的符號引用的具體字面值
  #41 = Utf8               com/ikang/JVM/classfile/ClassFile
   //第42個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 com/ikang/JVM/classfile/ClassFile
   //這個常量被我們的第6個常量持有,這明顯表示儲存本Object類的符號引用的具體字面值
  #42 = Utf8               java/lang/Object
   //第43個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 2222222
   //這個常量被我們的第10個常量持有,這明顯表示儲存常量J的具體值
  #43 = Utf8               2222222
   //第44個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 java/lang/Throwable
   //這個常量被我們的第28個常量持有,這明顯表示儲存Exception的父類Throwable類的符號引用具體值
  #44 = Utf8               java/lang/Throwable
   //第45個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 java/lang/Throwable
   //這個常量被我們的第30個常量持有,這明顯表示儲存Exception類的符號引用具體值
  #45 = Utf8               java/lang/Exception
   //第46個常量是CONSTANT_utf8_info類型,表示UTF-8編碼的字符串,用來存儲具體的字符串字面值 這裏的值是 printStackTrace
   //這個常量被我們的第40個常量持有,這明顯表示printStackTrace方法名的具體值
  #46 = Utf8               printStackTrace
//class常量池結束,後面是字段表和方法表
{ 
  //常量字段J
  public static final java.lang.String J;
    //常量字段J的字段描述符  String類型
descriptor: Ljava/lang/String;
//常量字段J的訪問標記 public static final
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
//常量字段J的額外ConstantValue屬性,該屬性中又包括常量字段的類型和具體值,這裏可以看出來,對於常量字段在編譯時就確定了值,對於常量字段,在類加載的準備階段就已經通過ConstantValue初始化爲最終值了,對常量字段的引用不會導致類進入“初始化”階段。
    ConstantValue: String 2222222
  /*方法表的默認構造方法開始*/
  public com.ikang.JVM.classfile.ClassFile();
    //方法描述符
descriptor: ()V
//方法訪問標記
flags: ACC_PUBLIC
//方法Code屬性
Code:
  //棧最大深度1,局部變量最大數量1,參數1
      stack=1, locals=1, args_size=1
      /*下面是方法字節碼,對於方法字節碼,在後面提出來重點說明*/
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      //行號表, 用於描述Java源代碼行號與字節碼行號(字節碼偏移量)之間的對應關係,每一行第一個數字對應代碼行數,第二個數字對應前面code中字節碼字節碼指令左邊的數字。一行可以對應多個字節碼指令。
      LineNumberTable:
        line 4: 0
      //局部變量表, start+length表示這個變量在字節碼中的生命週期起始和結束的偏移位置,Name就是變量的名字,slot就是這個變量在局部變量表中的槽位(槽位可複用,從0開始),name就是變量名稱,Signatur局部變量的字段類型描述符; 
//下面的含義就是名叫this的局部變量的字節碼生命週期從頭0到結尾5(不包括),佔用第一個槽位(索引0),變量是ClassFile類型。
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/ikang/JVM/classfile/ClassFile;
/*方法表的默認構造方法 結束*/
/*方法表的getk方法 開始*/
  public int getK();
    //方法描述符,很簡單,表示返回值爲int ,參數爲空
descriptor: ()I
//訪問標記  public
flags: ACC_PUBLIC
//Code屬性,包括方法字節碼,行號表,局部變量表
Code:
  //棧最大深度1,局部變量最大數量1,參數1
      stack=1, locals=1, args_size=1
        /*下面是方法字節碼,對於方法字節碼,在後面提出來重點說明*/
         0: aload_0
         1: getfield      #2                  // Field k:I
         4: ireturn
      //行號表
      LineNumberTable:
        line 10: 0
      //局部變量表,只有一個this變量
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/ikang/JVM/classfile/ClassFile;
/*方法表的getk方法 結束*/
/*方法表的setK方法 開始*/
  public void setK(int) throws java.lang.Exception;
    //方法描述符,很簡單,表示返回值爲void ,參數爲int
descriptor: (I)V
//訪問標記 public
flags: ACC_PUBLIC
//Code屬性,包括方法字節碼,行號表,局部變量表,該方法還包括異常表、棧圖
Code:
  //棧最大深度2,局部變量最大數量4,參數2
      stack=2, locals=4, args_size=2
          /*下面是方法字節碼,對於方法字節碼,在後面提出來重點說明*/
         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field k:I
         5: goto          19
         8: astore_2
         9: aload_2
        10: invokevirtual #4                  // Method java/lang/IllegalStateException.printStackTrace:()V
        13: goto          19
        16: astore_3
        17: aload_3
        18: athrow
        19: return
      //異常表屬性, 在Java虛擬機中,處理異常(catch語句)不是由字節碼指令來實現的,而是採用異常表來完成的。異常表是由try-catch語句生成的。具體結構在class文件結構那篇文章處有介紹。
      Exception table:
         from    to  target type
         //第一行表示 0~5(不包括) 行出現的 IllegalStateException 異常,直接跳轉到 8(astore_2) 行;java代碼層面就是:如果try語句塊中出現屬於IllegalStateException或其子類的異常,則轉到catch語句塊處理。
             0     5     8   Class java/lang/IllegalStateException
         //第二行表示 0~5(不包括)  行出現的 其餘的所有異常, 直接跳轉到 16(astore_3) 行,java代碼層面就是:如果try語句塊中出現不屬於IllegalStateException或其子類的異常,則轉到finally語句塊處理。從這裏可以看出,finally關鍵字的語義也是由異常表實現的。
             0     5    16   any
         //第三行表示 8~13(不包括)  行出現的 其餘的所有異常, 直接跳轉到 16(astore_3) 行;java代碼層面就是:如果catch語句塊中出現任何異常,則轉到finally語句塊處理。
             8    13    16   any
      //行號表
      LineNumberTable:
        line 16: 0
        line 20: 5
        line 17: 8
        line 18: 9
        line 20: 13
        line 19: 16
        line 21: 19
      //局部變量表,有3個局部變量
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            9       4     2     e   Ljava/lang/IllegalStateException;
            0      20     0  this   Lcom/ikang/JVM/classfile/ClassFile;
            0      20     1     k   I
      //棧圖,該屬性不包含運行時所需的信息,僅僅用於用於加快Class文件的類型檢驗。
      StackMapTable: number_of_entries = 3
        frame_type = 72 /* same_locals_1_stack_item */
          stack = [ class java/lang/IllegalStateException ]
        frame_type = 71 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 2 /* same */
    //列舉異常,表示一個方法可能拋出的異常,通常是由方法的throws 關鍵字指定的,這和Code屬性中的異常表不一樣。
    Exceptions:
      throws java.lang.Exception
/*方法表的setK方法 結束*/
/*方法表的main方法 開始*/
  public static void main(java.lang.String[]);
    //方法描述符,很簡單,表示返回值爲void ,參數爲String[]
descriptor: ([Ljava/lang/String;)V
//訪問標記,public static
flags: ACC_PUBLIC, ACC_STATIC
//code屬性
    Code:
      stack=0, locals=1, args_size=1
        //由於沒有代碼,因此只有return一個字節碼,表示方法的結束.
         0: return
      LineNumberTable:
        line 24: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  args   [Ljava/lang/String;
}
//記錄class文件的源文件 ClassFile.java
SourceFile: "ClassFile.java"

2.2.2.1 構造方法的字節碼解析

0: aload_0
1: invokespecial #1                  // Method java/lang/Object."<init>":()V
4: return

  左邊的數字表示字節碼偏移量(從0開始),字節碼的偏移量和前面的字節碼長度有關係,例如aload_0是第一個字節碼,自然偏移量是0,它本身佔一個字節,因此 invokespecial就從索引1開始,invokespecial本身佔據一個字節,但是接收一個兩個字節的無符號數參數,因此整個invokespecial指令佔據3個字節,故後續的return指令位於偏移量4處。
  在最開始時,局部變量表中具有一個變量this。局部變量表第0項爲this引用,表示當前對象。在Java 中, 對千所有的非靜態函數調用, 爲了能順利訪問this對象,都會將對象的引用放置在局部變量表第0 個槽位,如下圖:
在這裏插入圖片描述
  執行第一條指令,aload_0,表示把局部變量第 1 個引用型局部變量推到操作數棧,即把this對象引用壓入操作數棧的棧頂(操作數棧相當於虛擬機的工作區,主要用於字節碼的執行——大多數指令都要從這裏先壓入原始數據,然後彈出數據,執行運算,然後把結果壓回操作數棧,最後把結果賦給局部變量表的變量),如下圖:
在這裏插入圖片描述
  執行第二條指令,invokespecial, 表示對棧頂對象的私有、構造、父類方法進行調用,這些方法的特點是調用目標在編譯時就確定了,對這些方法的調用又稱爲“解析”。該指令接收一個2個字節長度的無符號參數,用於構建一個當前類的運行時常量池的索引值,該索引所指向的運行時常量池項應當是一個方法的符號引用,表示將執行棧頂對象對應的該方法。
  javap已經幫我們算出來了,該索引就是值爲1的索引處的常量項,我們回去看看第一項常量,是< init >方法的符號引用。即調用實例初始化方法(< init >)來完成對象的初始化,就是this指定的對象的調用< init >方法完成初始化。

  第二條指令執行完,即該類對象被初始化完畢,執行第三條指令,return,表示方法結束,並從當前方法返回 void,並清空棧空間數據包括操作數棧和局部變量表。
  從上面的三條指令可以看出來,在無參構造方法調用了< init >方法對對象進行初始化, < init >方法用於將對象字段的零值初始化爲指定值。

2.2.2.2 getK方法的字節碼解析

0: aload_0
1: getfield      #2                  // Field k:I
4: ireturn

  在最開始時,同樣局部變量表中具有一個變量this,操作數棧爲空。
在這裏插入圖片描述
  執行第一個指令,aload_0,表示把 局部變量第1個引用型局部變量推到操作數棧頂 ,即把this對象引用壓入操作數棧的棧頂。
在這裏插入圖片描述
  執行第二個指令,getfield,表示 獲取指定對象的字段值,並將其值壓入棧頂 。該指令接收一個2個字節長度的無符號參數,用於構建一個當前類的運行時常量池的索引值,該索引所指向的運行時常量池項應當是一個字段的符號引用,指令執行後,該字段的值將被取出,並壓入到操作數棧頂。很明顯,這裏指向第二項常量,我們回去看看第二項常量,的確是k字段 的符號引用。第二個指令執行完畢後如下圖:
在這裏插入圖片描述
  執行第三個指令,ireturn,表示結束方法並返回一個int 類型數據。很明顯是將操作數棧的棧頂值返回(對於執行引擎來說,活動線程中,只有棧頂的棧幀是有效的,稱爲當前棧幀,執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作)。

2.2.2.3 getKsetK方法的字節碼

         0: aload_0
         1: iload_1
         2: putfield      #2                  // Field k:I
         5: goto          19
         8: astore_2
         9: aload_2
        10: invokevirtual #4                  // Method java/lang/IllegalStateException.printStackTrace:()V
        13: goto          19
        16: astore_3
        17: aload_3
        18: athrow
        19: return
      //異常表屬性, 在Java虛擬機中,處理異常(catch語句)不是由字節碼指令來實現的,而是採用異常表來完成的。異常表是由try-catch語句生成的。具體結構在class文件結構處有介紹。
      Exception table:
         from    to  target type
         //第一行表示 0~5(不包括) 行出現的 IllegalStateException 異常,直接跳轉到 8(astore_2) 行;java代碼層面就是:如果try語句塊中出現屬於IllegalStateException或其子類的異常,則轉到catch語句塊處理。
             0     5     8   Class java/lang/IllegalStateException
         //第二行表示 0~5(不包括) 行出現的 其餘的所有異常, 直接跳轉到 16(astore_3) 行,java代碼層面就是:如果try語句塊中出現不屬於IllegalStateException或其子類的異常,則轉到finally語句塊處理。從這裏可以看出,finally關鍵字的語義也是由異常表實現的。
             0     5    16   any
         //第三行表示 8~13(不包括)行出現的 其餘的所有異常, 直接跳轉到 16(astore_3) 行;java代碼層面就是:如果catch語句塊中出現任何異常,則轉到finally語句塊處理。
             8    13    16   any

  該方法的字節碼明顯變多了,主要加了一些異常處理邏輯。不過一個一個看,也很簡單。
  執行第1個指令,aload_0,表示把局部變量第1個引用型局部變量推到操作數棧頂,即把this對象引用壓入操作數棧的棧頂。
  執行第2個指令,iload_1,表示把局部變量第2個int型局部變量推到操作數棧頂,即把k字段的值壓入操作數棧的棧頂。
在這裏插入圖片描述
  執行第3個指令,putfield,表示設置對象字段的值。該指令接收一個2個字節長度的無符號參數,用於構建一個當前類的運行時常量池的索引值,該索引所指向的運行時常量池項應當是一個字段的符號引用,指令執行後,操作數棧中的該字段所屬的對象this和具體的值,將被彈出棧。putfield的參數指向常量池第二個常量,明顯表示k字段。
在這裏插入圖片描述
  執行第4個指令,goto,表示無條件分支跳轉。 該指令接收一個2個字節長度的有符號參數,用於構建一個16 位有符號的分支偏移量。指令執行後,程序將會轉到這個goto 指令之後的,由上述偏移量確定的目標地址上繼續執行。這個目標地址必須處於goto 指令所在的方法之中。
  很明顯,後面的偏移量是19,這說明爲k賦值之後,就可以執行return了。因爲本代碼finally塊中沒有定義代碼,實際上如果finally中有代碼,那麼將會在每一個goto之前插入finally的字節碼,這也從字節碼的層面保證了finally語句塊必須執行!

執行第5個指令,astore_2,表示。把棧頂引用型數值存入第3個局部變量表位置
執行第6個指令,aload_2,表示把局部變量第3個引用型局部變量推到操作數棧
執行第7個指令,invokevirtual,表示調用實例方法,依據實例的類型進行方法分派。該指令接收一個2個字節長度的無符號參數,用於構建一個當前類的運行時常量池的索引值,該索引所指向的運行時常量池項應當是一個方法的符號引用,這裏指向第四個常量,我們返回查看常量池,發現是printStackTrace方法的符號引用。

  實際上,上面表格部分的字節碼,就是catch塊中的內容。我們根據異常表,當發生IllegalStateException異常,將會跳轉到astore_2,執行catch中的內容。
  查看局部變量表可知,實際上astore_2是將IllegalStateException異常引用存放了局部變量表的第三個位置
  第6、7個指令實際上就是在調用printStackTrace方法
  執行第8個指令,goto,表示無條件分支跳轉。這是第二個goto指令,在代碼邏輯層面:表示catch執行完畢,finally語句塊執行完畢(本finally中沒有代碼),方法結束。

  第9個指令,astore_3, 表示把棧頂引用型數值存入第4個局部變量表位置。能夠執行到這個指令,根據異常表,說明catch中出現了異常或者出現不屬於IllegalStateException或其子類的異常,將該異常類型,放到第四個局部變量表位置。我們去查看局部變量表,並沒有第四個異常變量,這說明這一段字節碼的邏輯不一定會執行,在編譯時還不確定該異常的類型。
  第10個指令,aload_3, 表示把局部變量第4個引用型局部變量推到操作數棧。即將上面的異常,放到操作數棧。但是在該指令之前還會執行finally中的字節碼。
  第11個指令,athrow, 表示將棧頂的異常拋出。即將上面的未能使用catch捕獲的異常拋出,並結束方法。
  第12個指令,return,作爲方法結束的標誌
  到此,setk方法結束。

補充:
如果在finally中添加一個輸出語句:System.out.println(11);那麼字節碼將會如下:
在這裏插入圖片描述
  我們可以看到,finally中的字節碼被插入到每一個可能的分支的最後,代碼層面就表示:無論如何,finally中代碼都將被執行,這是jvm在編譯時爲我們做出的字節碼級別的保證。

3 jclasslib替代javap

  實際上,現在通過jclasslib工具可以完全替代javap命令,jclasslib可用於用於打開class文件,而且是可視化的,效果更好一些。jclasslib還提供了修改jar包中的Class文件的API
  爲什麼先介紹比較麻煩的javap?因爲javap是Java官方自帶的,先了解原生的工具之後,再來使用jclasslib,我們將會更加的得心應手。
  jclasslib的github地址:jclasslib

3.1 idea安裝jclasslib插件

  1. 使用 ALT+CTRL+S 打開setting
  2. 選擇plugins(插件),搜索jclasslib,然後安裝,重啓
    在這裏插入圖片描述
  3. 選擇要打開的class文件,點擊上面的view,點擊show bytecode with jclasslib,在右側就會出現可視化界面
    在這裏插入圖片描述
  4. 右側的可視化界面,可以看出來還是很簡單的,這裏就不介紹了。
    在這裏插入圖片描述

3.2 直接安裝jclasslib

  當然有些開發者沒有使用idea, github上這裏也可以直接安裝jclasslib。
  下載地址:https://github.com/ingokegel/jclasslib/releases
  安裝好之後,將class文件放入jclasslib中,可以看到,界面和idea中的界面差不多。
在這裏插入圖片描述
  如果由於某些神奇的原因,github上展示無法下載,可以使用本人提供的鏈接:
jclasslib

3.3 修改class文件字節碼案例

3.3.1 準備對比輸出

  因爲修改class文件的代碼使用到了同名的類,爲了不引起混淆,將原代碼的類名改爲ClassFile1,然後main方法添加如下代碼:

public static void main(String[] args) {
    System.out.println(J);
}

  就是簡單的打印常量J的值運行輸出:2222222。
  拿到class文件和源碼放到一個路徑下面。
在這裏插入圖片描述
  在包路徑開始處,運行cmd,嘗試使用命令行帶包執行class文件:
在這裏插入圖片描述
  可見還是輸出爲2222222

3.3.2 修改class文件

  我們此次作簡單的修改,將輸出2222222改成3333。
  我們的jclasslib已經提供了修改class文件的API。在jclasslib的安裝目錄中找到lib目錄,將下面的jar包拷貝到項目中:
在這裏插入圖片描述
  編寫代碼:

public class ModifyClass {
    public static void main(String[] args) throws Exception {
        FileInputStream fis = null;
        try {
            //自己的class文件路徑
            String filePath = "J:\\Idea\\jvm\\src\\main\\java\\com\\ikang\\JVM\\classfile\\ClassFile1.class";
            fis = new FileInputStream(filePath);
            DataInput di = new DataInputStream(fis);
            ClassFile cf = new ClassFile();
            cf.read(di);
            CPInfo[] infos = cf.getConstantPool();
            //找到常量池46個位置,CONSTANT_Utf-8_info所以這裏要用這個
            ConstantUtf8Info uInfo = (ConstantUtf8Info) infos[46];
            //設置新值
            uInfo.setBytes("3333".getBytes());
            //替換回去
            infos[46] = uInfo;
            cf.setConstantPool(infos);
            File f = new File(filePath);
            ClassFileWriter.writeToFile(f, cf);
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }
}

  爲什麼找到46個位置呢,因爲要修改的J常量的“2222222”字面量值就是存在第46個常量中:
在這裏插入圖片描述
  運行之後,再次使用java命令執行原來的class文件:
在這裏插入圖片描述
  結果輸出3333,修改成功。

4 附:字節碼指令表

  字節碼指令根據功能、屬性不同,可以分爲11大類。下面附上字節碼指令的分類,用於簡單、臨時查看,字節碼指令的詳細介紹,還需要查看官網的介紹。

4.1 Constants 常量相關

十進制 操作碼 助記符 含義
00 0x00 nop 什麼都不做
01 0x01 aconst_null 把 null 推到操作數棧
02 0x02 iconst_m1 把 int 常量 –1 推到操作數棧
03 0x03 iconst_0 把 int 常量 0 推到操作數棧
04 0x04 iconst_1 把 int 常量 1 推到操作數棧
05 0x05 iconst_2 把 int 常量 2 推到操作數棧
06 0x06 iconst_3 把 int 常量 3 推到操作數棧
07 0x07 iconst_4 把 int 常量 4 推到操作數棧
08 0x08 iconst_5 把 int 常量 5 推到操作數棧
09 0x09 lconst_0 把 long 常量 0 推到操作數棧
10 0x0A lconst_1 把 long 常量 1 推到操作數棧
11 0x0B fconst_0 把 float 常量 0 推到操作數棧
12 0x0C fconst_1 把 float 常量 1 推到操作數棧
13 0x0D fconst_2 把 float 常量 2 推到操作數棧
14 0x0E dconst_0 把 double 常量 0 推到操作數棧
15 0x0F dconst_1 把 double 常量 1 推到操作數棧
16 0x10 bipush 把單字節常量(-128~127)推到操作數棧
17 0x11 sipush 把 short 常量(-32768~32767)推到操作數棧
18 0x12 ldc 把常量池中的int,float,String型常量取出並推到操作數棧頂
19 0x13 ldc_w 把常量池中的int,float,String型常量取出並推到操作數棧頂(寬索引)
20 0x14 ldc2_w 把常量池中的long,double型常量取出並推到操作數棧頂(寬索引)

4.2 Loads 加載相關

十進制 操作碼 助記符 含義
21 0x15 iload 把 int 型局部變量推到操作數棧
22 0x16 lload 把 long 型局部變量推到操作數棧
23 0x17 fload 把 float 型局部變量推到操作數棧
24 0x18 dload 把 double 型局部變量推到操作數棧
25 0x19 aload 把引用型局部變量推到操作數棧
26 0x1A iload_0 把局部變量第 1 個 int 型局部變量推到操作數棧
27 0x1B iload_1 把局部變量第 2 個 int 型局部變量推到操作數棧
28 0x1C iload_2 把局部變量第 3 個 int 型局部變量推到操作數棧
29 0x1D iload_3 把局部變量第 4 個 int 型局部變量推到操作數棧
30 0x1E lload_0 把局部變量第 1 個 long 型局部變量推到操作數棧
31 0x1F lload_1 把局部變量第 2 個 long 型局部變量推到操作數棧
32 0x20 lload_2 把局部變量第 3 個 long 型局部變量推到操作數棧
33 0x21 lload_3 把局部變量第 4 個 long 型局部變量推到操作數棧
34 0x22 fload_0 把局部變量第 1 個 float 型局部變量推到操作數棧
35 0x23 fload_1 把局部變量第 2 個 float 型局部變量推到操作數棧
36 0x24 fload_2 把局部變量第 3 個 float 型局部變量推到操作數棧
37 0x25 fload_3 把局部變量第 4 個 float 型局部變量推到操作數棧
38 0x26 dload_0 把局部變量第 1 個 double 型局部變量推到操作數棧
39 0x27 dload_1 把局部變量第 2 個 double 型局部變量推到操作數棧
40 0x28 dload_2 把局部變量第 3 個 double 型局部變量推到操作數棧
41 0x29 dload_3 把局部變量第 4 個 double 型局部變量推到操作數棧
42 0x2A aload_0 把局部變量第 1 個引用型局部變量推到操作數棧
43 0x2B aload_1 把局部變量第 2 個引用型局部變量推到操作數棧
44 0x2C aload_2 把局部變量第 3 個引用型局部變量推到操作數棧
45 0x2D aload_3 把局部變量第 4 個引用 型局部變量推到操作數棧
46 0x2E iaload 把 int 型數組指定索引的值推到操作數棧
47 0x2F laload 把 long 型數組指定索引的值推到操作數棧
48 0x30 faload 把 float 型數組指定索引的值推到操作數棧
49 0x31 daload 把 double 型數組指定索引的值推到操作數棧
50 0x32 aaload 把引用型數組指定索引的值推到操作數棧
51 0x33 baload 把 boolean或byte型數組指定索引的值推到操作數棧
52 0x34 caload 把 char 型數組指定索引的值推到操作數棧
53 0x35 saload 把 short 型數組指定索引的值推到操作數棧

4.3 Store 存儲相關

十進制 操作碼 助記符 含義
54 0x36 istore 把棧頂 int 型數值存入指定局部變量
55 0x37 lstore 把棧頂 long 型數值存入指定局部變量
56 0x38 fstore 把棧頂 float 型數值存入指定局部變量
57 0x39 dstore 把棧頂 double 型數值存入指定局部變量
58 0x3A astore 把棧頂引用型數值存入指定局部變量
59 0x3B istore_0 把棧頂 int 型數值存入第 1 個局部變量
60 0x3C istore_1 把棧頂 int 型數值存入第 2 個局部變量
61 0x3D istore_2 把棧頂 int 型數值存入第 3 個局部變量
62 0x3E istore_3 把棧頂 int 型數值存入第 4 個局部變量
63 0x3F lstore_0 把棧頂 long 型數值存入第 1 個局部變量
64 0x40 lstore_1 把棧頂 long 型數值存入第 2 個局部變量
65 0x41 lstore_2 把棧頂 long 型數值存入第 3 個局部變量
66 0x42 lstore_3 把棧頂 long 型數值存入第 4 個局部變量
67 0x43 fstore_0 把棧頂 float 型數值存入第 1 個局部變量
68 0x44 fstore_1 把棧頂 float 型數值存入第 2 個局部變量
69 0x45 fstore_2 把棧頂 float 型數值存入第 3 個局部變量
70 0x46 fstore_3 把棧頂 float 型數值存入第 4 個局部變量
71 0x47 dstore_0 把棧頂 double 型數值存入第 1 個局部變量
72 0x48 dstore_1 把棧頂 double 型數值存入第 2 個局部變量
73 0x49 dstore_2 把棧頂 double 型數值存入第 3 個局部變量
74 0x4A dstore_3 把棧頂 double 型數值存入第 4 個局部變量
75 0x4B astore_0 把棧頂 引用 型數值存入第 1 個局部變量
76 0x4C astore_1 把棧頂 引用 型數值存入第 2 個局部變量
77 0x4D astore_2 把棧頂 引用 型數值存入第 3 個局部變量
78 0x4E astore_3 把棧頂 引用 型數值存入第 4 個局部變量
79 0x4F iastore 把棧頂 int 型數值存入數組指定索引位置
80 0x50 lastore 把棧頂 long 型數值存入數組指定索引位置
81 0x51 fastore 把棧頂 float 型數值存入數組指定索引位置
82 0x52 dastore 把棧頂 double 型數值存入數組指定索引位置
83 0x53 aastore 把棧頂 引用 型數值存入數組指定索引位置
84 0x54 bastore 把棧頂 boolean or byte 型數值存入數組指定索引位置
85 0x55 castore 把棧頂 char 型數值存入數組指定索引位置
86 0x56 sastore 把棧頂 short 型數值存入數組指定索引位置

4.4 Stack 棧相關

十進制 操作碼 助記符 含義
87 0x57 pop 把棧頂數值彈出(非long,double數值)
88 0x58 pop2 把棧頂的一個long或double值彈出,或彈出2個其他類型數值
89 0x59 dup 複製棧頂數值並把數值入棧
90 0x5A dup_x1 複製棧頂數值並將兩個複製值壓入棧頂
91 0x5B dup_x2 複製棧頂數值並將三個(或兩個)複製值壓入棧頂
92 0x5C dup2 複製棧頂一個(long 或double 類型的)或兩個(其它)數值並將複製值壓入棧頂
93 0x5D dup2_x1 dup_x1 指令的雙倍版本
94 0x5E dup2_x2 dup_x2 指令的雙倍版本
95 0x5F swap 把棧頂端的兩個數的值交換(數值不能是long 或double 類型< td >的)

4.5 Math 運算相關

  Java 虛擬機在處理浮點數運算時,不會拋出任何運行時異常,當一個操作產生溢出時,將會使用有符號的無窮大來表示,如果某個操作結果沒有明確的數學定義的話,將會使用 NaN 值來表示。所有使用 NaN 值作爲操作數的算術操作,結果都會返回 NaN。

十進制 操作碼 助記符 含義
96 0x60 iadd 把棧頂兩個 int 型數值相加並將結果入棧
97 0x61 ladd 把棧頂兩個 long 型數值相加並將結果入棧
98 0x62 fadd 把棧頂兩個 float 型數值相加並將結果入棧
99 0x63 dadd 把棧頂兩個 double 型數值相加並將結果入棧
100 0x64 isub 把棧頂兩個 int 型數值相減並將結果入棧
101 0x65 lsub 把棧頂兩個 long 型數值相減並將結果入棧
102 0x66 fsub 把棧頂兩個 float 型數值相減並將結果入棧
103 0x67 dsub 把棧頂兩個 double 型數值相減並將結果入棧
104 0x68 imul 把棧頂兩個 int 型數值相乘並將結果入棧
105 0x69 lmul 把棧頂兩個 long 型數值相乘並將結果入棧
106 0x6A fmul 把棧頂兩個 float 型數值相乘並將結果入棧
107 0x6B dmul 把棧頂兩個 double 型數值相乘並將結果入棧
108 0x6C idiv 把棧頂兩個 int 型數值相除並將結果入棧
109 0x6D ldiv 把棧頂兩個 long 型數值相除並將結果入棧
110 0x6E fdiv 把棧頂兩個 float 型數值相除並將結果入棧
111 0x6F ddiv 把棧頂兩個 double 型數值相除並將結果入棧
112 0x70 irem 把棧頂兩個 int 型數值模運算並將結果入棧
113 0x71 lrem 把棧頂兩個 long 型數值模運算並將結果入棧
114 0x72 frem 把棧頂兩個 float 型數值模運算並將結果入棧
115 0x73 drem 把棧頂兩個 double 型數值模運算並將結果入棧
116 0x74 ineg 把棧頂 int 型數值取負並將結果入棧
117 0x75 lneg 把棧頂 long 型數值取負並將結果入棧
118 0x76 fneg 把棧頂 float 型數值取負並將結果入棧
119 0x77 dneg 把棧頂 double 型數值取負並將結果入棧
120 0x78 ishl 把 int 型數左移指定位數並將結果入棧
121 0x79 lshl 把 long 型數左移指定位數並將結果入棧
122 0x7A ishr 把 int 型數右移指定位數並將結果入棧(有符號)
123 0x7B lshr 把 long 型數右移指定位數並將結果入棧(有符號)
124 0x7C iushr 把 int 型數右移指定位數並將結果入棧(無符號)
125 0x7D lushr 把 long 型數右移指定位數並將結果入棧(無符號)
126 0x7E iand 把棧頂兩個 int 型數值 按位與 並將結果入棧
127 0x7F land 把棧頂兩個 long 型數值 按位與 並將結果入棧
128 0x80 ior 把棧頂兩個 int 型數值 按位或 並將結果入棧
129 0x81 lor 把棧頂兩個 long 型數值 按或與 並將結果入棧
130 0x82 ixor 把棧頂兩個 int 型數值 按位異或 並將結果入棧
131 0x83 lxor 把棧頂兩個 long 型數值 按位異或 並將結果入棧
132 0x84 iinc 把指定 int 型增加指定值

4.6 Conversions 轉換相關

  類型轉換指令可以將兩種不同的數值類型進行相互轉換,這些轉換操作一般用於實現用戶代碼中的顯示類型轉換操作。
  Java 虛擬機直接支持(即轉換時無需顯示的轉換指令)小範圍類型向大範圍類型的安全轉換,但在處理窄化類型轉換時,必須顯式使用轉換指令來完成。

十進制 操作碼 助記符 含義
133 0x85 i2l 把棧頂 int 強轉 long 併入棧
134 0x86 i2f 把棧頂 int 強轉 float 併入棧
135 0x87 i2d 把棧頂 int 強轉 double 併入棧
136 0x88 l2i 把棧頂 long 強轉 int 併入棧
137 0x89 l2f 把棧頂 long 強轉 float 併入棧
138 0x8A l2d 把棧頂 long 強轉 double 併入棧
139 0x8B f2i 把棧頂 float 強轉 int 併入棧
140 0x8C f2l 把棧頂 float 強轉 long 併入棧
141 0x8D f2d 把棧頂 float 強轉 double 併入棧
142 0x8E d2i 把棧頂 double 強轉 int 併入棧
143 0x8F d2l 把棧頂 double 強轉 long 併入棧
144 0x90 d2f 把棧頂 double 強轉 float 併入棧
145 0x91 i2b 把棧頂 int 強轉 byte 併入棧
146 0x92 i2c 把棧頂 int 強轉 char 併入棧
147 0x93 i2s 把棧頂 int 強轉 short 併入棧

4.7 Comparisons 比較相關

十進制 操作碼 助記符 含義
148 0x94 lcmp 比較棧頂兩long 型數值大小,並將結果(1,0,-1)壓入棧頂
149 0x95 fcmpl 比較棧頂兩float 型數值大小,並將結果(1,0,-1)壓入棧頂;當其中一個數值爲“NaN”時,將-1 壓入棧頂
150 0x96 fcmpg 比較棧頂兩float 型數值大小,並將結果(1,0,-1)壓入棧頂;當其中一個數值爲“NaN”時,將1 壓入棧頂
151 0x97 dcmpl 比較棧頂兩double 型數值大小,並將結果(1,0,-1)壓入棧頂;當其中一個數值爲“NaN”時,將-1 壓入棧頂
152 0x98 dcmpg 比較棧頂兩double 型數值大小,並將結果(1,0,-1)壓入棧頂;當其中一個數值爲“NaN”時,將1 壓入棧頂
153 0x99 ifeq 當棧頂 int 型數值等於0時,跳轉
154 0x9A ifne 當棧頂 int 型數值不等於0時,跳轉
155 0x9B iflt 當棧頂 int 型數值小於0時,跳轉
156 0x9C ifge 當棧頂 int 型數值大於等於0時,跳轉
157 0x9D ifgt 當棧頂 int 型數值大於0時,跳轉
158 0x9E ifle 當棧頂 int 型數值小於等於0時,跳轉
159 0x9F if_icmpeq 比較棧頂兩個 int 型數值,等於0時,跳轉
160 0xA0 if_icmpne 比較棧頂兩個 int 型數值,不等於0時,跳轉
161 0xA1 if_icmplt 比較棧頂兩個 int 型數值,小於0時,跳轉
162 0xA2 if_icmpge 比較棧頂兩個 int 型數值,大於等於0時,跳轉
163 0xA3 if_icmpgt 比較棧頂兩個 int 型數值,大於0時,跳轉
164 0xA4 if_icmple 比較棧頂兩個 int 型數值,小於等於0時,跳轉
165 0xA5 if_acmpeq 比較棧頂兩個 引用 型數值,相等時跳轉
166 0xA6 if_acmpne 比較棧頂兩個 引用 型數值,不相等時跳轉

4.8 Control 控制相關

  控制轉移指令可以讓 Java 虛擬機有條件或無條件地從指定的位置指令而不是控制轉移指令的下一條指令繼續執行程序,從概念模型上理解,可以認爲控制轉移指令就是在有條件或無條件地修改 PC 寄存器的值。

十進制 操作碼 助記符 含義
167 0xA7 goto 無條件分支跳轉
168 0xA8 jsr 跳轉至指定16 位offset(bit) 位置,並將jsr 下一條指令地址壓入棧頂
169 0xA9 ret 返回至局部變量指定的index 的指令位置(一般與jsr,jsr_w聯合使用)
170 0xAA tableswitch 用於switch 條件跳轉,case 值連續(可變長度指令)
171 0xAB lookupswitch 用於switch 條件跳轉,case 值不連續(可變長度指令)
172 0xAC ireturn 結束方法,並返回一個int 類型數據
173 0xAD lreturn 從當前方法返回 long
174 0xAE freturn 從當前方法返回 float
175 0xAF dreturn 從當前方法返回 double
176 0xB0 areturn 從當前方法返回 對象引用
177 0xB1 return 從當前方法返回 void

4.9 references 引用、方法、異常、同步相關

十進制 操作碼 助記符 含義
178 0xB2 getstatic 獲取指定類的靜態域,並將其值壓入棧頂
179 0xB3 putstatic 爲類的靜態域賦值
180 0xB4 getfield 獲取指定類的實例域(對象的字段值),並將其值壓入棧頂
181 0xB5 putfield 爲指定的類的實例域賦值
182 0xB6 invokevirtual 調用對象的實例方法,根據對象的實際類型進行分派(虛方法分派),是Java語言中最常見的方法分派方式。
183 0xB7 invokespecial 調用一些需要特殊處理的實例方法,包括實例初始化方法()、私有方法和父類方法。這三類方法的調用對象在編譯時就可以確定。
184 0xB8 invokestatic 調用靜態方法
185 0xB9 invokeinterface 調用接口方法調,它會在運行時搜索一個實現了這個接口方法的對象,找出適合的方法進行調用。
186 0xBA invokedynamic 調用動態鏈接方法(該指令是指令是Java SE 7 中新加入的)。用於在運行時動態解析出調用點限定符所引用的方法,並執行該方法,前面4條調用指令的分派邏輯都固化在Java虛擬機內部,而invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的。
187 0xBB new 創建一個對象,並將其引用值壓入棧頂
188 0xBC newarray 創建一個指定原始類型(如int、float、char……)的數組,並將其引用值壓入棧頂
189 0xBD anewarray 創建一個引用型(如類,接口,數組)的數組,並將其引用值壓入棧頂
190 0xBE arraylength 獲得數組的長度值並壓入棧頂
191 0xBF athrow 將棧頂的異常直接拋出。Java程序中顯式拋出異常的操作(throw語句)都由athrow指令來實現,並且,在Java虛擬機中,處理異常(catch語句)不是由字節碼指令來實現的,而是採用異常表來完成的。
192 0xC0 checkcast 檢驗類型轉換,檢驗未通過將拋出ClassCastException
193 0xC1 instanceof 檢驗對象是否是指定的類的實例,如果是將1 壓入棧頂,否則將0 壓入棧頂
194 0xC2 monitorenter 獲取對象的monitor,用於同步塊或同步方法
195 0xC3 monitorexit 釋放對象的monitor,用於同步塊或同步方法

  Java 虛擬機可以支持方法級的同步和方法內部一段指令序列的同步,這兩種同步結構都是使用管程(Monitor)來支持的。
  **方法級的同步是隱式的,即無須通過字節碼指令來控制,它實現在方法調用和返回操作之中。**虛擬機可以從方法常量池的方法表結構中的 ACC_SYNCHRONIZED 方法標誌得知一個方法是否聲明爲同步方法。當方法調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設置了,執行線程就要求先成功持有管程,然後才能執行方法,最後當方法完成(無論是正常完成還是非正常完成)時釋放管程。在方法執行期間,執行線程持有了管程,其他任何線程都無法再獲取到同一個管程。如果一個同步方法執行期間拋出了異常,並且在方法內部無法處理此異常,那麼這個  同步方法所持有的管程將在異常拋到同步方法之外時自動釋放。
  同步一段指令集序列通常是由Java語言中的synchronized語句塊來表示的,Java虛擬機的指令集中有monitorenter和monitorexit兩條指令來支持synchronized關鍵字的語義
  編譯器必須確保無論方法通過何種方式完成,方法中調用過的每條monitorenter指令都必須執行其對應的monitorexit指令,而無論這個方法是正常結束還是異常結束。

4.10 Extended 擴展相關

十進制 操作碼 助記符 含義
196 0xC4 wide 擴展訪問局部變量表的索引寬度
197 0xC5 multianewarray 創建指定類型和指定維度的多維數組(執行該指令時,操作棧中必須包含各維度的長度值),並將其引用值壓入棧頂
198 0xC6 ifnull 爲 null 時跳轉
199 0xC7 ifnonnull 非 null 時跳轉
200 0xC8 goto_w 無條件跳轉(寬索引)
201 0xC9 jsr_w 跳轉指定32bit偏移位置,並將jsr_w下一條指令地址入棧

4.11 Reserved 保留指令

十進制 操作碼 助記符 含義
202 0xCA breakpoint 調試時的斷點
254 0xFE impdep1 用於在特定硬件中使用的語言後門
255 0xFF impdep2 用於在特定硬件中使用的語言後門

5 參考和學習

    JVM規範 Java SE8官方文檔
    JVM規範中《操作碼助記符表》
    JVM規範中《JVM指令集》介紹(包括操作碼對應的操作數)
  《Java虛擬機規範》
  《深入理解Java虛擬機》
  《實戰Java虛擬機》

如果有什麼不懂或者需要交流,可以留言。另外希望點贊、收藏、關注,我將不間斷更新各種Java學習博客!

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