Java 指令與字節碼

鑑於十進制的計算機還遙遙無期,我們目前的計算機都是二進制的計算機,而二進制的計算機僅能識別0和1的信號。經過0和1的多位組合又可以產生更多不同的信號。另外,現在計算機領域通過進行0和1的多位組合表示對字符進行編碼(例如Unicode),我們的計算機可以處理字符。同樣的,通過不同的0和1的多位組合可以產生不同的計算機指令所以我們的計算機可以處理多樣的CPU指令。

計算機可以直接處理的CPU指令我們稱爲機器碼,機器碼是離CPU指令最接近的編碼,是CPU可以直接解讀的指令,但是由於CPU廠商的不同,針對不同的CPU有不同的指令集,所以我們爲了能夠在不同的CPU上運行我們的代碼,我們需要將我們的代碼編譯成不同的機器碼。

Java的使命是1次編寫、到處執行。在不同的硬件平臺,不同的操作系統上均可以順暢運行。如何實現跨平臺呢?JVM應用而生。我們只需要將我們的Java代碼編寫爲class二進制字節碼文件,由JVM將字節碼解釋執行,屏蔽操作系統硬件平臺不同的影響。如果是熱點代碼,再通過JIT動態編譯爲機器碼,提高執行效率。

查看class文件

編寫簡單java代碼

public class HelloWorld {
    private int num;

    public void sayHello(){
        num += 1;
        System.out.println("Say Hello "+num);
    }

    public static void main(String[] args){
        new HelloWorld().sayHello();
    }

}

編譯代碼

javac HelloWorld.java

查看class文件

vim HelloWorld.class
Êþº¾^@^@^@6^@6
^@      ^@^U    ^@^F^@^V        ^@^W^@^X^R^@^@^@^\
^@^]^@^^^G^@^_
^@^F^@^U
^@^F^@ ^G^@!^A^@^Cnum^A^@^AI^A^@^F<init>^A^@^C()V^A^@^DCode^A^@^OLineNumberTable^A^@^HsayHello^A^@^Dmain^A^@^V([Ljava/lang/String;)V^A^@
SourceFile^A^@^OHelloWorld.java^L^@^L^@^M^L^@
^@^K^G^@"^L^@#^@$^A^@^PBootstrapMethods^O^F^@%^H^@&^L^@'^@(^G^@)^L^@*^@+^A^@
HelloWorld^L^@^P^@^M^A^@^Pjava/lang/Object^A^@^Pjava/lang/System^A^@^Cout^A^@^ULjava/io/PrintStream;
^@,^@-^A^@^KSay Hello ^A^A^@^WmakeConcatWithConstants^A^@^U(I)Ljava/lang/String;^A^@^Sjava/io/PrintStream^A^@^Gprintln^A^@^U(Ljava/lang/String;)V^G^@.^L^@'^@2^A^@$java/lang/invoke/StringConcatFactory^G^@4^A^@^FLookup^A^@^LInnerClasses^A^@<98>(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;^G^@5^A^@%java/lang/invoke/MethodHandles$Lookup^A^@^^java/lang/invoke/MethodHandles^@!^@^F^@  ^@^@^@^A^@^B^@
^@^K^@^@^@^C^@^A^@^L^@^M^@^A^@^N^@^@^@^]^@^A^@^A^@^@^@^E*·^@^A±^@^@^@^A^@^O^@^@^@^F^@^A^@^@^@^A^@^A^@^P^@^M^@^A^@^N^@^@^@:^@^C^@^A^@^@^@^Z*Y´^@^B^D`µ^@^B²^@^C*´^@^Bº^@^D^@^@¶^@^E±^@^@^@^A^@^O^@^@^@^N^@^C^@^@^@^E^@
^@^F^@^Y^@^G^@  ^@^Q^@^R^@^A^@^N^@^@^@'^@^B^@^A^@^@^@^K»^@^FY·^@^G¶^@^H±^@^@^@^A^@^O^@^@^@
^@^B^@^@^@
^@
^@^K^@^C^@^S^@^@^@^B^@^T^@1^@^@^@
^@^A^@/^@3^@0^@^Y^@^Y^@^@^@^H^@^A^@^Z^@^A^@^[

看起來就喝亂碼一樣是不是,這是因爲vim編輯器無法查看二進制文件,所以我們輸入 :%!xxd 查看轉16進製表示的class文件

00000000: cafe babe 0000 0036 0036 0a00 0900 1509  .......6.6......
00000010: 0006 0016 0900 1700 1812 0000 001c 0a00  ................
00000020: 1d00 1e07 001f 0a00 0600 150a 0006 0020  ...............
00000030: 0700 2101 0003 6e75 6d01 0001 4901 0006  ..!...num...I...
00000040: 3c69 6e69 743e 0100 0328 2956 0100 0443  <init>...()V...C
00000050: 6f64 6501 000f 4c69 6e65 4e75 6d62 6572  ode...LineNumber
00000060: 5461 626c 6501 0008 7361 7948 656c 6c6f  Table...sayHello
00000070: 0100 046d 6169 6e01 0016 285b 4c6a 6176  ...main...([Ljav
00000080: 612f 6c61 6e67 2f53 7472 696e 673b 2956  a/lang/String;)V
00000090: 0100 0a53 6f75 7263 6546 696c 6501 000f  ...SourceFile...
000000a0: 4865 6c6c 6f57 6f72 6c64 2e6a 6176 610c  HelloWorld.java.
000000b0: 000c 000d 0c00 0a00 0b07 0022 0c00 2300  ..........."..#.
000000c0: 2401 0010 426f 6f74 7374 7261 704d 6574  $...BootstrapMet
000000d0: 686f 6473 0f06 0025 0800 260c 0027 0028  hods...%..&..'.(
000000e0: 0700 290c 002a 002b 0100 0a48 656c 6c6f  ..)..*.+...Hello
000000f0: 576f 726c 640c 0010 000d 0100 106a 6176  World........jav
00000100: 612f 6c61 6e67 2f4f 626a 6563 7401 0010  a/lang/Object...
00000110: 6a61 7661 2f6c 616e 672f 5379 7374 656d  java/lang/System
00000120: 0100 036f 7574 0100 154c 6a61 7661 2f69  ...out...Ljava/i
00000130: 6f2f 5072 696e 7453 7472 6561 6d3b 0a00  o/PrintStream;..
00000140: 2c00 2d01 000b 5361 7920 4865 6c6c 6f20  ,.-...Say Hello 
00000150: 0101 0017 6d61 6b65 436f 6e63 6174 5769  ....makeConcatWi
00000160: 7468 436f 6e73 7461 6e74 7301 0015 2849  thConstants...(I
00000170: 294c 6a61 7661 2f6c 616e 672f 5374 7269  )Ljava/lang/Stri
00000180: 6e67 3b01 0013 6a61 7661 2f69 6f2f 5072  ng;...java/io/Pr
00000190: 696e 7453 7472 6561 6d01 0007 7072 696e  intStream...prin
000001a0: 746c 6e01 0015 284c 6a61 7661 2f6c 616e  tln...(Ljava/lan
000001b0: 672f 5374 7269 6e67 3b29 5607 002e 0c00  g/String;)V.....
000001c0: 2700 3201 0024 6a61 7661 2f6c 616e 672f  '.2..$java/lang/
000001d0: 696e 766f 6b65 2f53 7472 696e 6743 6f6e  invoke/StringCon
000001e0: 6361 7446 6163 746f 7279 0700 3401 0006  catFactory..4...
000001f0: 4c6f 6f6b 7570 0100 0c49 6e6e 6572 436c  Lookup...InnerCl
00000200: 6173 7365 7301 0098 284c 6a61 7661 2f6c  asses...(Ljava/l
00000210: 616e 672f 696e 766f 6b65 2f4d 6574 686f  ang/invoke/Metho
00000220: 6448 616e 646c 6573 244c 6f6f 6b75 703b  dHandles$Lookup;
00000230: 4c6a 6176 612f 6c61 6e67 2f53 7472 696e  Ljava/lang/Strin
00000240: 673b 4c6a 6176 612f 6c61 6e67 2f69 6e76  g;Ljava/lang/inv
00000250: 6f6b 652f 4d65 7468 6f64 5479 7065 3b4c  oke/MethodType;L
00000260: 6a61 7661 2f6c 616e 672f 5374 7269 6e67  java/lang/String
00000270: 3b5b 4c6a 6176 612f 6c61 6e67 2f4f 626a  ;[Ljava/lang/Obj
00000280: 6563 743b 294c 6a61 7661 2f6c 616e 672f  ect;)Ljava/lang/
00000290: 696e 766f 6b65 2f43 616c 6c53 6974 653b  invoke/CallSite;
000002a0: 0700 3501 0025 6a61 7661 2f6c 616e 672f  ..5..%java/lang/
000002b0: 696e 766f 6b65 2f4d 6574 686f 6448 616e  invoke/MethodHan
000002c0: 646c 6573 244c 6f6f 6b75 7001 001e 6a61  dles$Lookup...ja
000002d0: 7661 2f6c 616e 672f 696e 766f 6b65 2f4d  va/lang/invoke/M
000002e0: 6574 686f 6448 616e 646c 6573 0021 0006  ethodHandles.!..
000002f0: 0009 0000 0001 0002 000a 000b 0000 0003  ................
00000300: 0001 000c 000d 0001 000e 0000 001d 0001  ................
00000310: 0001 0000 0005 2ab7 0001 b100 0000 0100  ......*.........
00000320: 0f00 0000 0600 0100 0000 0100 0100 1000  ................
00000330: 0d00 0100 0e00 0000 3a00 0300 0100 0000  ........:.......
00000340: 1a2a 59b4 0002 0460 b500 02b2 0003 2ab4  .*Y....`......*.
00000350: 0002 ba00 0400 00b6 0005 b100 0000 0100  ................
00000360: 0f00 0000 0e00 0300 0000 0500 0a00 0600  ................
00000370: 1900 0700 0900 1100 1200 0100 0e00 0000  ................
00000380: 2700 0200 0100 0000 0bbb 0006 59b7 0007  '...........Y...
00000390: b600 08b1 0000 0001 000f 0000 000a 0002  ................
000003a0: 0000 000a 000a 000b 0003 0013 0000 0002  ................
000003b0: 0014 0031 0000 000a 0001 002f 0033 0030  ...1......./.3.0
000003c0: 0019 0019 0000 0008 0001 001a 0001 001b  ................
000003d0: 0a

Java的所有指令大約有200個,一個字節(8位)可以存儲256中不同的指令,一個這樣的字節稱爲字節碼(ByteCode),所以,class文件稱爲二進制字節碼文件。

當我們用16進制格式查看class文件,由於

15*16^0+15*16^1
255

1*2^0+1*2^1+1*2^2+1*2^3+1*2^4+1*2^5+1*2^6+1*2^7
255

即16進制模式下,2個數字相當於計算機2進制一個字節。
由此理解,我們則可以很方便的理解class文件的16進製表示。起始的4個字節非常特殊,即ca fe ba be,ca fe ba be是Gosling定義的魔法樹,意思是Coffee Baby,其十進制值是340569151582。它的作用是:標誌該文件是一個Java類文件,如果沒有識別到該標誌,說明該文件不是Java類或者文件已受損,無法進行加載。後面緊跟着的4個字節00 00 00 36代表的是當前版本號,==00 00 ==代表副版本號,00 36代表主版本號,00 36的十進制爲54,是JDK10的內部版本號。

Java字節碼總的結構表

我們應該如何解析二進制的class自己碼呢,對照下面Java字節碼結構表將會方便很多。

類型 名稱 說明 長度
u4 magic 魔數,識別Class文件格式 4個字節
u2 minor_version 副版本號 2個字節
u2 major_version 主版本號 2個字節
u2 constant_pool_count 常量池計算器 2個字節
cp_info constant_pool 常量池 n個字節
u2 access_flags 訪問標誌 2個字節
u2 this_class 類索引 2個字節
u2 super_class 父類索引 2個字節
u2 interfaces_count 接口計數器 2個字節
u2 interfaces 接口索引集合 2個字節
u2 fields_count 字段個數 2個字節
field_info fields 字段集合 n個字節
u2 methods_count 方法計數器 2個字節
method_info methods 方法集合 n個字節
u2 attributes_count 附加屬性計數器 2個字節
attribute_info attributes 附加屬性集合 n個字節

從Java字節碼結構表中,我們可以發現class文件只有兩種數據類型:無符號數和表。如下表所示:

數據類型 定義 說明
無符號數 無符號數可以用來描述數字、索引引用、數量值或按照utf-8編碼構成的字符串值。 其中無符號數屬於基本的數據類型。以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節和8個字節
表是由多個無符號數或其他表構成的複合數據結構。 所有的表都以“_info”結尾。由於表沒有固定長度,所以通常會在其前面加上個數說明。

以下內容參考自<<從一個class文件深入理解Java字節碼結構>>

常量池

常量池容量計數器

接下來就是常量池了。由於常量池的數量不固定,時長時短,所以需要放置兩個字節來表示常量池容量計數值。Demo的值爲:
在這裏插入圖片描述
其值爲0x0036,掐指一算,也就是54。
需要注意的是,這實際上只有53項常量。爲什麼呢?

通常我們寫代碼時都是從0開始的,但是這裏的常量池卻是從1開始,因爲它把第0項常量空出來了。這是爲了在於滿足後面某些指向常量池的索引值的數據在特定情況下需要表達“不引用任何一個常量池項目”的含義,這種情況可用索引值0來表示。
Class文件中只有常量池的容量計數是從1開始的,對於其他集合類型,包括接口索引集合、字段表集合、方法表集合等的容量計數都與一般習慣相同,是從0開始的。

字面量和符號引用

在對這些常量解讀前,我們需要搞清楚幾個概念。
常量池主要存放兩大類常量:字面量符號引用。如下表:

常量 具體的常量
字面量 文本字符串
聲明爲final的常量值
符號引用 類和接口的全限定名
字段的名稱和描述符
方法的名稱和描述符

全限定名

java/lang/System這個就是類的全限定名,僅僅是把包名的".“替換成”/",爲了使連續的多個全限定名之間不產生混淆,在使用時最後一般會加入一個“;”表示全限定名結束。

簡單名稱

簡單名稱是指沒有類型和參數修飾的方法或者字段名稱,上面例子中的類的sayHello()方法和num字段的簡單名稱分別是sayHello和num。

描述符

描述符的作用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。根據描述符規則,基本數據類型(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示,詳見下表:

標誌符 含義
B 基本數據類型byte
C 基本數據類型char
D 基本數據類型double
F 基本數據類型float
I 基本數據類型int
J 基本數據類型long
S 基本數據類型short
Z 基本數據類型boolean
V 基本數據類型void
L 對象類型,如Ljava/lang/Object

對於數組類型,每一維度將使用一個前置的[字符來描述,如一個定義爲java.lang.String[][]類型的二維數組,將被記錄爲:[[Ljava/lang/String;一個整型數組int[]被記錄爲[I
用描述符來描述方法時,按照先參數列表,後返回值的順序描述,參數列表按照參數的嚴格順序放在一組小括號“( )”之內。如方法java.lang.String toString()的描述符爲( ) LJava/lang/String;,方法int abc(int[] x, int y)的描述符爲([II) I

常量類型和結構

常量池中的每一項都是一個表,其項目類型共有14種,如下表格所示:

類型 標誌 描述
CONSTANT_utf8_info 1 UTF-8編碼的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮點型字面量
CONSTANT_Long_info 5 長整型字面量
CONSTANT_Double_info 6 雙精度浮點型字面量
CONSTANT_Class_info 7 類或接口的符號引用
CONSTANT_String_info 8 字符串類型字面量
CONSTANT_Fieldref_info 9 字段的符號引用
CONSTANT_Methodref_info 10 類中方法的符號引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符號引用
CONSTANT_NameAndType_info 12 字段或方法的符號引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MothodType_info 16 標誌方法類型
CONSTANT_InvokeDynamic_info 18 表示一個動態方法調用點

這14種類型的結構各不相同,如下表格所示 (這裏的類型保持疑問,應該是字節大小byte而不是bit位)
在這裏插入圖片描述

從上面的表格可以看到,雖然每一項的結構都各不相同,但是他們有個共同點,就是每一項的第一個字節都是一個標誌位,標識這一項是哪種類型的常量。

常量解讀

好了,我們進入這53項常量的解讀,首先是第一個常量,看下它的標誌位是啥:

在這裏插入圖片描述
其值爲0x0a,即10,查上面的表格可知,其對應的項目類型爲CONSTANT_Methodref_info,即類中方法的符號引用。其結構爲:
在這裏插入圖片描述

即後面4個字節都是它的內容,分別爲兩個索引項:
在這裏插入圖片描述

其中前兩位的值爲0x0009,即9,指向常量池第9項的索引;
後兩位的值爲0x0015,即21,指向常量池第21項的索引。
至此,第一個常量就解讀完畢了。
我們再來看下第二個常量,看下它的標誌位是啥::
在這裏插入圖片描述

其標誌位的值爲0x09,即9,查上面的表格可知,其對應的項目類型爲CONSTANT_Fieldref_info,即字段的符號引用。其結構爲:
在這裏插入圖片描述

同樣也是4個字節,前後都是兩個索引。
在這裏插入圖片描述
分別指向第6項的索引和第22項的索引。
後面還有51項常量就不一一去解讀了,因爲整個常量池還是挺長的:

實際上,我們只要敲一行簡單的命令:

javap -verbose HelloWorld.class

其中輸出結果爲:

Classfile /data/code/java/test/HelloWorld.class
  Last modified 2019年5月9日; size 976 bytes
  MD5 checksum 51cee0be0709b813fb444ca3621391ad
  Compiled from "HelloWorld.java"
public class HelloWorld
  minor version: 0
  major version: 54
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #6                          // HelloWorld
  super_class: #9                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 3, attributes: 3
Constant pool:
   #1 = Methodref          #9.#21         // java/lang/Object."<init>":()V
   #2 = Fieldref           #6.#22         // HelloWorld.num:I
   #3 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = InvokeDynamic      #0:#28         // #0:makeConcatWithConstants:(I)Ljava/lang/String;
   #5 = Methodref          #29.#30        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = Class              #31            // HelloWorld
   #7 = Methodref          #6.#21         // HelloWorld."<init>":()V
   #8 = Methodref          #6.#32         // HelloWorld.sayHello:()V
   #9 = Class              #33            // java/lang/Object
  #10 = Utf8               num
  #11 = Utf8               I
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               sayHello
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               SourceFile
  #20 = Utf8               HelloWorld.java
  #21 = NameAndType        #12:#13        // "<init>":()V
  #22 = NameAndType        #10:#11        // num:I
  #23 = Class              #34            // java/lang/System
  #24 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
  #25 = Utf8               BootstrapMethods
  #26 = MethodHandle       6:#37          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #27 = String             #38            // Say Hello \u0001
  #28 = NameAndType        #39:#40        // makeConcatWithConstants:(I)Ljava/lang/String;
  #29 = Class              #41            // java/io/PrintStream
  #30 = NameAndType        #42:#43        // println:(Ljava/lang/String;)V
  #31 = Utf8               HelloWorld
  #32 = NameAndType        #16:#13        // sayHello:()V
  #33 = Utf8               java/lang/Object
  #34 = Utf8               java/lang/System
  #35 = Utf8               out
  #36 = Utf8               Ljava/io/PrintStream;
  #37 = Methodref          #44.#45        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #38 = Utf8               Say Hello \u0001
  #39 = Utf8               makeConcatWithConstants
  #40 = Utf8               (I)Ljava/lang/String;
  #41 = Utf8               java/io/PrintStream
  #42 = Utf8               println
  #43 = Utf8               (Ljava/lang/String;)V
  #44 = Class              #46            // java/lang/invoke/StringConcatFactory
  #45 = NameAndType        #39:#50        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #46 = Utf8               java/lang/invoke/StringConcatFactory
  #47 = Class              #52            // java/lang/invoke/MethodHandles$Lookup
  #48 = Utf8               Lookup
  #49 = Utf8               InnerClasses
  #50 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #51 = Class              #53            // java/lang/invoke/MethodHandles
  #52 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #53 = Utf8               java/lang/invoke/MethodHandles
{
  public HelloWorld();
    descriptor: ()V
    flags: (0x0001) 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 1: 0

  public void sayHello();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field num:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field num:I
        10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: aload_0
        14: getfield      #2                  // Field num:I
        17: invokedynamic #4,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
        22: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 5: 0
        line 6: 10
        line 7: 25

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #6                  // class HelloWorld
         3: dup
         4: invokespecial #7                  // Method "<init>":()V
         7: invokevirtual #8                  // Method sayHello:()V
        10: return
      LineNumberTable:
        line 10: 0
        line 11: 10
}
SourceFile: "HelloWorld.java"
InnerClasses:
  public static final #48= #47 of #51;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #26 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #27 Say Hello \u0001

你看,一家大小,齊齊整整,全都出來了。
但是,通過我們手動去分析才知道這個結果是怎麼出來的,要知其然知其所以然嘛~

訪問標誌

常量池後面就是訪問標誌,用兩個字節來表示,其標識了類或者接口的訪問信息,比如:該Class文件是類還是接口,是否被定義成public,是否是abstract,如果是類,是否被聲明成final等等。各種訪問標誌如下所示:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 是否爲Public類型
ACC_FINAL 0x0010 是否被聲明爲final,只有類可以設置
ACC_SUPER 0x0020 是否允許使用invokespecial字節碼指令的新語義,JDK1.0.2之後編譯出來的類的這個標誌默認爲真
ACC_INTERFACE 0x0200 標誌這是一個接口
ACC_ABSTRACT 0x0400 是否爲abstract類型,對於接口或者抽象類來說,次標誌值爲真,其他類型爲假
ACC_SYNTHETIC 0x1000 標誌這個類並非由用戶代碼產生
ACC_ANNOTATION 0x2000 標誌這是一個註解
ACC_ENUM x4000 標誌這是一個枚舉

再來看下我們Demo字節碼中的值:
在這裏插入圖片描述

其值爲:0x0021,是0x00200x0001的並集,即這是一個Public的類,再回頭看看我們的源碼。
確認過眼神,我遇上對的了。

類索引、父類索引、接口索引

  • 訪問標誌後的兩個字節就是類索引;
  • 類索引後的兩個字節就是父類索引;
  • 父類索引後的兩個字節則是接口索引計數器。

通過這三項,就可以確定了這個類的繼承關係了。

類索引

我們直接來看下Demo字節碼中的值:
在這裏插入圖片描述

類索引的值爲0x0006,即爲指向常量池中第6項的索引。你看,這裏用到了常量池中的值了。
我們回頭翻翻常量池中的第6項:

   #6 = Class              #31            // HelloWorld

通過類索引我們可以確定到類的全限定名。

父類索引

從上圖看到,父類索引的值爲0x0009,即常量池中的第四項:

   #9 = Class              #33            // java/lang/Object

這樣我們就可以確定到父類的全限定名。

可以看到,如果我們沒有繼承任何類,其默認繼承的是java/lang/Object類。
同時,由於Java不支持多繼承,所以其父類只有一個。

接口計數器

從上圖看到,接口索引個數的值爲0x0000,即沒有任何接口索引,我們demo的源碼也確實沒有去實現任何接口。

接口索引集合

由於我們demo的源碼沒有去實現任何接口,所以接口索引集合就爲空了,不佔地方,嘻嘻。
可以看到,由於Java支持多接口,因此這裏設計成了接口計數器和接口索引集合來實現。

字段表

接口計數器或接口索引集合後面就是字段表了。
字段表用來描述類或者接口中聲明的變量。這裏的字段包含了類級別變量以及實例變量,但是不包括方法內部聲明的局部變量。

字段表計數器

同樣,其前面兩個字節用來表示字段表的容量,看下demo字節碼中的值:

在這裏插入圖片描述

其值爲0x0001,表示只有一個字段。

字段表訪問標誌

我們知道,一個字段可以被各種關鍵字去修飾,比如:作用域修飾符(public、private、protected)、static修飾符、final修飾符、volatile修飾符等等。因此,其可像類的訪問標誌那樣,使用一些標誌來標記字段。字段的訪問標誌有如下這些:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 字段是否爲public
ACC_PRIVATE 0x0002 字段是否爲private
ACC_PROTECTED 0x0004 字段是否爲protected
ACC_STATIC 0x0008 字段是否爲static
ACC_FINAL 0x0010 字段是否爲final
ACC_VOLATILE 0x0040 字段是否爲volatile
ACC_TRANSTENT 0x0080 字段是否爲transient
ACC_SYNCHETIC 0x1000 字段是否爲由編譯器自動產生
ACC_ENUM 0x4000 字段是否爲enum

字段表結構

字段表作爲一個表,同樣有他自己的結構:

類型 名稱 含義 數量
u2 access_flags 訪問標誌 1
u2 name_index 字段名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 屬性計數器 1
attribute_info attributes 屬性集合 attributes_count

字段表解讀

我們先來回顧一下我們demo源碼中的字段:

    private int num = 1;

由於只有一個字段,還是比較簡單的,直接看demo字節碼中的值:
在這裏插入圖片描述

訪問標誌的值爲0x0002,查詢上面字段訪問標誌的表格,可得字段爲private;
字段名索引的值爲0x000a,查詢常量池中的第10項,可得:

#10 = Utf8               num

描述符索引的值爲0x000b,查詢常量池中的第11項,可得:

#11 = Utf8               I

屬性計數器的值爲0x0000,即沒有任何的屬性。
確認過眼神,我遇上對的了。
至此,字段表解讀完成。

注意事項

  1. 字段表集合中不會列出從父類或者父接口中繼承而來的字段。
  2. 內部類中爲了保持對外部類的訪問性,會自動添加指向外部類實例的字段。
  3. 在Java語言中字段是無法重載的,兩個字段的數據類型,修飾符不管是否相同,都必須使用不一樣的名稱,但是對於字節碼來講,如果兩個字段的描述符不一致,那字段重名就是合法的.

方法表

字段表後就是方法表了。

方法表計數器

前面兩個字節依然用來表示方法表的容量,看下demo字節碼中的值:

在這裏插入圖片描述

其值爲0x0003,即有3個方法。

方法表訪問標誌

跟字段表一樣,方法表也有訪問標誌,而且他們的標誌有部分相同,部分則不同,方法表的具體訪問標誌如下:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 方法是否爲public
ACC_PRIVATE 0x0002 方法是否爲private
ACC_PROTECTED 0x0004 方法是否爲protected
ACC_STATIC 0x0008 方法是否爲static
ACC_FINAL 0x0010 方法是否爲final
ACC_SYHCHRONRIZED 0x0020 方法是否爲synchronized
ACC_BRIDGE 0x0040 方法是否是有編譯器產生的方法
ACC_VARARGS 0x0080 方法是否接受參數
ACC_NATIVE 0x0100 方法是否爲native
ACC_ABSTRACT 0x0400 方法是否爲abstract
ACC_STRICTFP 0x0800 方法是否爲strictfp
ACC_SYNTHETIC 0x1000 方法是否是有編譯器自動產生的

方法表結構

方法表的結構實際跟字段表是一樣的,方法表結構如下:

類型 名稱 含義 數量
u2 access_flags 訪問標誌 1
u2 name_index 方法名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 屬性計數器 1
attribute_info attributes 屬性集合 attributes_count

屬性解讀

還是先回顧一下Demo中的源碼:

    public void sayHello(){
        num += 1;
        System.out.println("Say Hello "+num);
    }

    public static void main(String[] args){
        new HelloWorld().sayHello();
    }

只有2個自定義的方法。但是上面方法表計數器明明是3個,這是爲啥呢?
這是因爲它包含了默認的構造方法,我們來看下下面的分析就懂了,先看下Demo字節碼中的值:

在這裏插入圖片描述

這是第一個方法表,我們來解讀一下這裏面的16進制:
訪問標誌的值爲0x0001,查詢上面字段訪問標誌的表格,可得字段爲public
方法名索引的值爲0x000c,查詢常量池中的第12項,可得:

  #12 = Utf8               <init>

這個名爲<init>的方法實際上就是默認的構造方法了。
描述符索引的值爲0x000d,查詢常量池中的第13項,可得:

  #13 = Utf8               ()V

屬性計數器的值爲0x0001,即這個方法表有一個屬性。
屬性計數器後面就是屬性表了,由於只有一個屬性,所以這裏也只有一個屬性表。
由於涉及到屬性表,這裏簡單說下,下一節會詳細介紹。

屬性表的前兩個字節是屬性名稱索引,這裏的值爲0x000e,查下常量池中的第14項:

  #14 = Utf8               Code

即這是一個Code屬性,我們方法裏面的代碼就是存放在這個Code屬性裏面。相關細節暫且不表。下一節會詳細介紹Code屬性。
先跳過屬性表,我們再來看下第二個方法:

16.字節碼-方法表2.png

訪問標誌的值爲0x0001,查詢上面字段訪問標誌的表格,可得字段爲public
方法名索引的值爲0x0010,查詢常量池中的第16項,可得:

  #16 = Utf8               sayHello

描述符索引的值爲0x000d,查詢常量池中的第13項,可得:

  #13 = Utf8               ()V

屬性計數器的值爲0x0001,即這個方法表有一個屬性。
屬性名稱索引的值同樣也是0x000e,即這是一個Code屬性。
可以看到,第二個方法表就是我們自定義的sayHello()方法了。

注意事項

如果父類方法在子類中沒有被重寫(Override),方法表集合中就不會出現父類的方法。
編譯器可能會自動添加方法,最典型的便是類構造方法(靜態構造方法)方法和默認實例構造方法方法。
在Java語言中,要重載(Overload)一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特徵簽名,特徵簽名就是一個方法中各個參數在常量池中的字段符號引用的集合,也就是因爲返回值不會包含在特徵簽名之中,因此Java語言裏無法僅僅依靠返回值的不同來對一個已有方法進行重載。但在Class文件格式中,特徵簽名的範圍更大一些,只要描述符不是完全一致的兩個方法就可以共存。也就是說,如果兩個方法有相同的名稱和特徵簽名,但返回值不同,那麼也是可以合法共存於同一個class文件中。

屬性表

前面說到了屬性表,現在來重點看下。屬性表不僅在方法表有用到,字段表和Class文件中也會用得到。本篇文章中用到的例子在字段表中的屬性個數爲0,所以也沒涉及到;在方法表中用到了2次,都是Code屬性;至於Class文件,在末尾時會講到,這裏就先不說了。

屬性類型

屬性表實際上可以有很多類型,上面看到的Code屬性只是其中一種,下面這些是虛擬機中預定義的屬性:

屬性名稱 使用位置 含義
Code 方法表 Java代碼編譯成的字節碼指令
ConstantValue 字段表 final關鍵字定義的常量池
Deprecated 類,方法,字段表 被聲明爲deprecated的方法和字段
Exceptions 方法表 方法拋出的異常
EnclosingMethod 類文件 僅當一個類爲局部類或者匿名類是才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法
InnerClass 類文件 內部類列表
LineNumberTable Code屬性 Java源碼的行號與字節碼指令的對應關係
LocalVariableTable Code屬性 方法的局部便狼描述
StackMapTable Code屬性 JDK1.6中新增的屬性,供新的類型檢查檢驗器檢查和處理目標方法的局部變量和操作數有所需要的類是否匹配
Signature 類,方法表,字段表 用於支持泛型情況下的方法簽名
SourceFile 類文件 記錄源文件名稱
SourceDebugExtension 類文件 用於存儲額外的調試信息
Synthetic 類,方法表,字段表 標誌方法或字段爲編譯器自動生成的
LocalVariableTypeTable 使用特徵簽名代替描述符,是爲了引入泛型語法之後能描述泛型參數化類型而添加
RuntimeVisibleAnnotations 類,方法表,字段表 爲動態註解提供支持
RuntimeInvisibleAnnotations 表,方法表,字段表 用於指明哪些註解是運行時不可見的
RuntimeVisibleParameterAnnotation 方法表 作用與RuntimeVisibleAnnotations屬性類似,只不過作用對象爲方法
RuntimeInvisibleParameterAnnotation 方法表 作用與RuntimeInvisibleAnnotations屬性類似,作用對象哪個爲方法參數
AnnotationDefault 方法表 用於記錄註解類元素的默認值
BootstrapMethods 類文件 用於保存invokeddynamic指令引用的引導方式限定符

屬性表結構

屬性表的結構比較靈活,各種不同的屬性只要滿足以下結構即可:

類型 名稱 數量 含義
u2 attribute_name_index 1 屬性名索引
u2 attribute_length 1 屬性長度
u1 info attribute_length 屬性表

即只需說明屬性的名稱以及佔用位數的長度即可,屬性表具體的結構可以去自定義。

部分屬性詳解

下面針對部分常見的一些屬性進行詳解

Code屬性

前面我們看到的屬性表都是Code屬性,我們這裏重點來看下。
Code屬性就是存放方法體裏面的代碼,像接口或者抽象方法,他們沒有具體的方法體,因此也就不會有Code屬性了。

Code屬性表結構

先來看下Code屬性表的結構,如下圖:

類型 數量 含義
u2 attribute_name_index 1 屬性名索引
u4 attribute_length 1 屬性長度
u2 max_stack 1 操作數棧深度的最大值
u2 max_locals 1 局部變量表所需的存續空間
u4 code_length 1 字節碼指令的長度
u1 code code_length 存儲字節碼指令
u2 exception_table_length 1 異常表長度
exception_info exception_table exception_length 異常表
u2 attributes_count 1 屬性集合計數器
attribute_info attributes attributes_count 屬性集合

可以看到:Code屬性表的前兩項跟屬性表是一致的,即Code屬性表遵循屬性表的結構,後面那些則是他自定義的結構。

Code屬性解讀

同樣,解讀Code屬性只需按照上面的表格逐一解讀即可。
我們先來看下第一個方法表中的Code屬性:

在這裏插入圖片描述

屬性名索引的值爲0x000e,上面也說過了,這是一個Code屬性;
屬性長度的值爲0x0000001d,即長度爲29,注意,這裏的長度是指後面自定義的屬性長度,不包括屬性名索引和屬性長度這兩個所佔的長度,因爲這哥倆佔的長度都是固定6個字節了,所以往後29個字節都是Code屬性的內容;
max_stack的值爲0x0001,即操作數棧深度的最大值爲2;
max_locals的值爲0x0001,即局部變量表所需的存儲空間爲1;max_locals的單位是Slot,Slot是虛擬機爲局部變量分配內存所使用的最小單位。
code_length的值爲0x000000005,即字節碼指令的5;
code的值爲0x2a b7 00 01 b1 這裏的值就代表一系列的字節碼指令。一個字節代表一個指令,一個指令可能有參數也可能沒參數,如果有參數,則其後面字節碼就是他的參數;如果沒參數,後面的字節碼就是下一條指令。

這裏我們來解讀一下這些指令,文末最後的附錄附有Java虛擬機字節碼指令表,可以通過指令表來查詢指令的含義。

  • 2a 指令,查表可得指令爲aload_0,其含義爲:將第0個Slot中爲reference類型的本地變量推送到操作數棧頂。

  • b7 指令,查表可得指令爲invokespecial,其含義爲:將操作數棧頂的reference類型的數據所指向的對象作爲方法接受者,調用此對象的實例構造器方法、private方法或者它的父類的方法。其後面緊跟着的2個字節即指向其具體要調用的方法。

    00 01,指向常量池中的第1項,查詢上面的常量池可得:

    #1 = Methodref          #9.#21         // java/lang/Object."<init>":()V
    

    即這是要調用默認構造方法。

  • b1 指令,查表可得指令爲return,含義從當前方法返回void

所以,上面的指令簡單點來說就是,調用默認的構造方法。
同時,可以看到,這些操作都是基於棧來完成的。

如果要逐字逐字的去查每一個指令的意思,那是相當的麻煩,大概要查到猴年馬月吧。實際上,只要一行命令,就能將這樣字節碼轉化爲指令了,還是javap命令哈:
javap -verbose Demo.class

截取部分輸出結果:

  public HelloWorld();
    descriptor: ()V
    flags: (0x0001) 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 1: 0

看看,那是相當的簡單。關於字節碼指令,就到此爲止了。繼續往下看。
exception_table_length的值爲0x0000,即異常表長度爲0,所以其異常表也就沒有了;
attributes_count的值爲0x0001,即code屬性表裏面還有一個其他的屬性表,後面就是這個其他屬性的屬性表了;
所有的屬性都遵循屬性表的結構,同樣,這裏的結構也不例外。
前兩個字節爲屬性名索引,其值爲0x000f,查看常量池中的第15項:

  #15 = Utf8               LineNumberTable

即這是一個LineNumberTable屬性。LineNumberTable屬性先跳過,具體可以看下一小節。
再來看下第二個方法表中的的Code屬性:
在這裏插入圖片描述

屬性名索引的值同樣爲0x000e,所以,這也是一個Code屬性;
屬性長度的值爲0x0000003a,即長度爲58;
max_stack的值爲0x0003,即操作數棧深度的最大值爲3;
max_locals的值爲0x0001,即局部變量表所需的存儲空間爲1;
code_length的值爲0x00000001a,即字節碼指令的26;
code的值爲0x2a 59 b4 00 02 04 60 b5 00 02 b2 00 03 2a b4 0002 ba 00 04 00 00 b6 b5 00 02 b2,使用javap命令,可得:

  public void sayHello();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field num:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field num:I
        10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: aload_0
        14: getfield      #2                  // Field num:I
        17: invokedynamic #4,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
        22: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 5: 0
        line 6: 10
        line 7: 25

可以看到,這就是我們自定義的sayHello()方法;
exception_table_length的值爲0x0000,即異常表長度爲0,所以其異常表也沒有;
attributes_count的值爲0x0001,即code屬性表裏面還有一個其他的屬性表;

屬性名索引值爲0x000a,即這同樣也是一個LineNumberTable屬性,LineNumberTable屬性看下一小節。

LineNumberTable屬性

LineNumberTable屬性是用來描述Java源碼行號與字節碼行號之間的對應關係。

LineNumberTable屬性表結構

類型 名稱 數量 含義
u2 attribute_name_index 1 屬性名索引
u4 attribute_length 1 屬性長度
u2 line_number_table_length 1 行號表長度
line_number_info line_number_table line_number_table_length 行號表

line_number_info(行號表),其長度爲4個字節,前兩個爲start_pc,即字節碼行號;後兩個爲line_number,即Java源代碼行號。

LineNumberTable屬性解讀

前面出現了兩個LineNumberTable屬性,先看第二個:
在這裏插入圖片描述

attributes_count的值爲0x0001,即code屬性表裏面還有一個其他的屬性表;
屬性名索引值爲0x000f,查看常量池中的第15項:

  #15 = Utf8               LineNumberTable

即這是一個LineNumberTable屬性。
attribute_length的值爲0x00 00 00 0e,即其長度爲14,後面14個字節的都是LineNumberTable屬性的內容;
line_number_table_length的值爲0x0003,即其行號表長度長度爲3,即有兩個行號表;
第一個行號表其值爲0x00 00 00 05,即字節碼第0行對應Java源碼第5行;
第二個行號表其值爲0x00 0a 00 06,即字節碼第6行對應Java源碼第10行。
第三個行號表其值爲0x00 19 00 07,即字節碼第7行對應Java源碼第25行。
同樣,使用javap命令也能看到:

      LineNumberTable:
        line 5: 0
        line 6: 10
        line 7: 25

第一個LineNumberTable屬性爲:

在這裏插入圖片描述

這裏就不逐一看了,同樣使用javap命令可得:

      LineNumberTable:
        line 1: 0

所以這些行號是有什麼用呢?當程序拋出異常時,我們就可以看到報錯的行號了,這利於我們debug;使用斷點時,也是根據源碼的行號來設置的。

SourceFile屬性

前面將常量池、字段集合、方法集合等都解讀完了。最終剩下的就是一些附加屬性了。
先來看看剩餘還未解讀的字節碼:

18.字節碼-附加屬性.png

同樣,前面2個字節表示附加屬性計算器,其值爲0x0001,即還有一個附加屬性。
最後這一個屬性就是SourceFile屬性,即源碼文件屬性。
先來看看其結構:

SourceFile屬性結構

類型|名稱|數量|含義
u2|attribute_name_index|1|屬性名索引
u4|attribute_length|1|屬性長度
u2|sourcefile_index|1|源碼文件索引
可以看到,其長度總是固定的8個字節。

SourceFile屬性解讀

屬性名索引的值爲0x0013,即常量池中的第19項,查詢可得:

  #19 = Utf8               SourceFile

屬性長度的值爲0x00 00 00 02,即長度爲2;
源碼文件索引的值爲0x0014,即常量池中的第20項,查詢可得:

  #20 = Utf8               HelloWorld.java

所以,我們能夠從這裏知道,這個Class文件的源碼文件名稱爲Demo.java。同樣,當拋出異常時,可以通過這個屬性定位到報錯的文件。
至此,上面的字節碼就完全解讀完畢了。

其他屬性

Java虛擬機中預定義的屬性有20多個,這裏就不一一介紹了,通過上面幾個屬性的介紹,只要領會其精髓,其他屬性的解讀也是易如反掌。

總結

通過手動去解讀字節碼文件,終於大概瞭解到其構成和原理了。
實際上,我們可以使用各種工具來幫我們去解讀字節碼文件,而不用直接去看這些16進制,神煩啊,哈哈。溜了溜了。

附錄

Java虛擬機字節碼指令表

字節碼 助記符 指令含義
0x00 nop 什麼都不做
0x01 aconst_null 將null推送至棧頂
0x02 iconst_m1 將int型-1推送至棧頂
0x03 iconst_0 將int型0推送至棧頂
0x04 iconst_1 將int型1推送至棧頂
0x05 iconst_2 將int型2推送至棧頂
0x06 iconst_3 將int型3推送至棧頂
0x07 iconst_4 將int型4推送至棧頂
0x08 iconst_5 將int型5推送至棧頂
0x09 lconst_0 將long型0推送至棧頂
0x0a lconst_1 將long型1推送至棧頂
0x0b fconst_0 將float型0推送至棧頂
0x0c fconst_1 將float型1推送至棧頂
0x0d fconst_2 將float型2推送至棧頂
0x0e dconst_0 將do le型0推送至棧頂
0x0f dconst_1 將do le型1推送至棧頂
0x10 bipush 將單字節的常量值(-128~127)推送至棧頂
0x11 sipush 將一個短整型常量值(-32768~32767)推送至棧頂
0x12 ldc 將int, float或String型常量值從常量池中推送至棧頂
0x13 ldc_w 將int, float或String型常量值從常量池中推送至棧頂(寬索引)
0x14 ldc2_w 將long或do le型常量值從常量池中推送至棧頂(寬索引)
0x15 iload 將指定的int型本地變量
0x16 lload 將指定的long型本地變量
0x17 fload 將指定的float型本地變量
0x18 dload 將指定的do le型本地變量
0x19 aload 將指定的引用類型本地變量
0x1a iload_0 將第一個int型本地變量
0x1b iload_1 將第二個int型本地變量
0x1c iload_2 將第三個int型本地變量
0x1d iload_3 將第四個int型本地變量
0x1e lload_0 將第一個long型本地變量
0x1f lload_1 將第二個long型本地變量
0x20 lload_2 將第三個long型本地變量
0x21 lload_3 將第四個long型本地變量
0x22 fload_0 將第一個float型本地變量
0x23 fload_1 將第二個float型本地變量
0x24 fload_2 將第三個float型本地變量
0x25 fload_3 將第四個float型本地變量
0x26 dload_0 將第一個do le型本地變量
0x27 dload_1 將第二個do le型本地變量
0x28 dload_2 將第三個do le型本地變量
0x29 dload_3 將第四個do le型本地變量
0x2a aload_0 將第一個引用類型本地變量
0x2b aload_1 將第二個引用類型本地變量
0x2c aload_2 將第三個引用類型本地變量
0x2d aload_3 將第四個引用類型本地變量
0x2e iaload 將int型數組指定索引的值推送至棧頂
0x2f laload 將long型數組指定索引的值推送至棧頂
0x30 faload 將float型數組指定索引的值推送至棧頂
0x31 daload 將do le型數組指定索引的值推送至棧頂
0x32 aaload 將引用型數組指定索引的值推送至棧頂
0x33 baload 將boolean或byte型數組指定索引的值推送至棧頂
0x34 caload 將char型數組指定索引的值推送至棧頂
0x35 saload 將short型數組指定索引的值推送至棧頂
0x36 istore 將棧頂int型數值存入指定本地變量
0x37 lstore 將棧頂long型數值存入指定本地變量
0x38 fstore 將棧頂float型數值存入指定本地變量
0x39 dstore 將棧頂do le型數值存入指定本地變量
0x3a astore 將棧頂引用型數值存入指定本地變量
0x3b istore_0 將棧頂int型數值存入第一個本地變量
0x3c istore_1 將棧頂int型數值存入第二個本地變量
0x3d istore_2 將棧頂int型數值存入第三個本地變量
0x3e istore_3 將棧頂int型數值存入第四個本地變量
0x3f lstore_0 將棧頂long型數值存入第一個本地變量
0x40 lstore_1 將棧頂long型數值存入第二個本地變量
0x41 lstore_2 將棧頂long型數值存入第三個本地變量
0x42 lstore_3 將棧頂long型數值存入第四個本地變量
0x43 fstore_0 將棧頂float型數值存入第一個本地變量
0x44 fstore_1 將棧頂float型數值存入第二個本地變量
0x45 fstore_2 將棧頂float型數值存入第三個本地變量
0x46 fstore_3 將棧頂float型數值存入第四個本地變量
0x47 dstore_0 將棧頂do le型數值存入第一個本地變量
0x48 dstore_1 將棧頂do le型數值存入第二個本地變量
0x49 dstore_2 將棧頂do le型數值存入第三個本地變量
0x4a dstore_3 將棧頂do le型數值存入第四個本地變量
0x4b astore_0 將>棧頂引用型數值存入第一個本地變量
0x4c astore_1 將棧頂引用型數值存入第二個本地變量
0x4d astore_2 將棧頂引用型數值存入第三個本地變量
0x4e astore_3 將棧頂引用型數值存入第四個本地變量
0x4f iastore 將棧頂int型數值存入指定數組的指定索引位置
0x50 lastore 將棧頂long型數值存入指定數組的指定索引位置
0x51 fastore 將棧頂float型數值存入指定數組的指定索引位置
0x52 dastore 將棧頂do le型數值存入指定數組的指定索引位置
0x53 aastore 將棧頂引用型數值存入指定數組的指定索引位置
0x54 bastore 將棧頂boolean或byte型數值存入指定數組的指定索引位置
0x55 castore 將棧頂char型數值存入指定數組的指定索引位置
0x56 sastore 將棧頂short型數值存入指定數組的指定索引位置
0x57 pop 將棧頂數值彈出 (數值不能是long或do le類型的)
0x58 pop2 將棧頂的一個(long或do le類型的)或兩個數值彈出(其它)
0x59 dup 複製棧頂數值並將複製值壓入棧頂
0x5a dup_x1 複製棧頂數值並將兩個複製值壓入棧頂
0x5b dup_x2 複製棧頂數值並將三個(或兩個)複製值壓入棧頂
0x5c dup2 複製棧頂一個(long或do le類型的)或兩個(其它)數值並將複製值壓入棧頂
0x5d dup2_x1 dup_x1 指令的雙倍版本
0x5e dup2_x2 dup_x2 指令的雙倍版本
0x5f swap 將棧最頂端的兩個數值互換(數值不能是long或do le類型的)
0x60 iadd 將棧頂兩int型數值相加並將結果壓入棧頂
0x61 ladd 將棧頂兩long型數值相加並將結果壓入棧頂
0x62 fadd 將棧頂兩float型數值相加並將結果壓入棧頂
0x63 dadd 將棧頂兩do le型數值相加並將結果壓入棧頂
0x64 is 將棧頂兩int型數值相減並將結果壓入棧頂
0x65 ls 將棧頂兩long型數值相減並將結果壓入棧頂
0x66 fs 將棧頂兩float型數值相減並將結果壓入棧頂
0x67 ds 將棧頂兩do le型數值相減並將結果壓入棧頂
0x68 imul 將棧頂兩int型數值相乘並將結果壓入棧頂
0x69 lmul 將棧頂兩long型數值相乘並將結果壓入棧頂
0x6a fmul 將棧頂兩float型數值相乘並將結果壓入棧頂
0x6b dmul 將棧頂兩do le型數值相乘並將結果壓入棧頂
0x6c idiv 將棧頂兩int型數值相除並將結果壓入棧頂
0x6d ldiv 將棧頂兩long型數值相除並將結果壓入棧頂
0x6e fdiv 將棧頂兩float型數值相除並將結果壓入棧頂
0x6f ddiv 將棧頂兩do le型數值相除並將結果壓入棧頂
0x70 irem 將棧頂兩int型數值作取模運算並將結果壓入棧頂
0x71 lrem 將棧頂兩long型數值作取模運算並將結果壓入棧頂
0x72 frem 將棧頂兩float型數值作取模運算並將結果壓入棧頂
0x73 drem 將棧頂兩do le型數值作取模運算並將結果壓入棧頂
0x74 ineg 將棧頂int型數值取負並將結果壓入棧頂
0x75 lneg 將棧頂long型數值取負並將結果壓入棧頂
0x76 fneg 將棧頂float型數值取負並將結果壓入棧頂
0x77 dneg 將棧頂do le型數值取負並將結果壓入棧頂
0x78 ishl 將int型數值左移位指定位數並將結果壓入棧頂
0x79 lshl 將long型數值左移位指定位數並將結果壓入棧頂
0x7a ishr 將int型數值右(符號)移位指定位數並將結果壓入棧頂
0x7b lshr 將long型數值右(符號)移位指定位數並將結果壓入棧頂
0x7c iushr 將int型數值右(無符號)移位指定位數並將結果壓入棧頂
0x7d lushr 將long型>數值右(無符號)移位指定位數並將結果壓入棧頂
0x7e iand 將棧頂兩int型數值作“按位與”並將結果壓入棧頂
0x7f land 將棧頂兩long型數值作“按位與”並將結果壓入棧頂
0x80 ior 將棧頂兩int型數值作“按位或”並將結果壓入棧頂
0x81 lor 將棧頂兩long型數值作“按位或”並將結果壓入棧頂
0x82 ixor 將棧頂兩int型數值作“按位異或”並將結果壓入棧頂
0x83 lxor 將棧頂兩long型數值作“按位異或”並將結果壓入棧頂
0x84 iinc 將指定int型變量增加指定值(i++, i–, i+=2)
0x85 i2l 將棧頂int型數值強制轉換成long型數值並將結果壓入棧頂
0x86 i2f 將棧頂int型數值強制轉換成float型數值並將結果壓入棧頂
0x87 i2d 將棧頂int型數值強制轉換成do le型數值並將結果壓入棧頂
0x88 l2i 將棧頂long型數值強制轉換成int型數值並將結果壓入棧頂
0x89 l2f 將棧頂long型數值強制轉換成float型數值並將結果壓入棧頂
0x8a l2d 將棧頂long型數值強制轉換成do le型數值並將結果壓入棧頂
0x8b f2i 將棧頂float型數值強制轉換成int型數值並將結果壓入棧頂
0x8c f2l 將棧頂float型數值強制轉換成long型數值並將結果壓入棧頂
0x8d f2d 將棧頂float型數值強制轉換成do le型數值並將結果壓入棧頂
0x8e d2i 將棧頂do le型數值強制轉換成int型數值並將結果壓入棧頂
0x8f d2l 將棧頂do le型數值強制轉換成long型數值並將結果壓入棧頂
0x90 d2f 將棧頂do le型數值強制轉換成float型數值並將結果壓入棧頂
0x91 i2b 將棧頂int型數值強制轉換成byte型數值並將結果壓入棧頂
0x92 i2c 將棧頂int型數值強制轉換成char型數值並將結果壓入棧頂
0x93 i2s 將棧頂int型數值強制轉換成short型數值並將結果壓入棧頂
0x94 lcmp 比較棧頂兩long型數值大小,並將結果(1,0,-1)壓入棧頂
0x95 fcmpl 比較棧頂兩float型數值大小,並將結果(1,0,-1)壓入棧頂;當其中一個數值爲NaN時,將-1壓入棧頂
0x96 fcmpg 比較棧頂兩float型數值大小,並將結果(1,0,-1)壓入棧頂;當其中一個數值爲NaN時,將1壓入棧頂
0x97 dcmpl 比較棧頂兩do le型數值大小,並將結果(1,0,-1)壓入棧頂;當其中一個數值爲NaN時,將-1壓入棧頂
0x98 dcmpg 比較棧頂兩do le型數值大小,並將結果(1,0,-1)壓入棧頂;當其中一個數值爲NaN時,將1壓入棧頂
0x99 ifeq 當棧頂int型數值等於0時跳轉
0x9a ifne 當棧頂int型數值不等於0時跳轉
0x9b iflt 當棧頂int型數值小於0時跳轉
0x9c ifge 當棧頂int型數值大於等於0時跳轉
0x9d ifgt 當棧頂int型數值大於0時跳轉
0x9e ifle 當棧頂int型數值小於等於0時跳轉
0x9f if_icmpeq 比較棧頂兩int型數值大小,當結果等於0時跳轉
0xa0 if_icmpne 比較棧頂兩int型數值大小,當結果不等於0時跳轉
0xa1 if_icmplt 比較棧頂兩int型數值大小,當結果小於0時跳轉
0xa2 if_icmpge 比較棧頂兩int型數值大小,當結果大於等於0時跳轉
0xa3 if_icmpgt 比較棧頂兩int型數值大小,當結果大於0時跳轉
0xa4 if_icmple 比較棧頂兩int型數值大小,當結果小於等於0時跳轉
0xa5 if_acmpeq 比較棧頂兩引用型數值,當結果相等時跳轉
0xa6 if_acmpne 比較棧頂兩引用型數值,當結果不相等時跳轉
0xa7 goto 無條件跳轉
0xa8 jsr 跳轉至指定16位offset位置,並將jsr下一條指令地址壓入棧頂
0xa9 ret 返回至本地變量
0xaa tableswitch 用於switch條件跳轉,case值連續(可變長度指令)
0xab lookupswitch 用於switch條件跳轉,case值不連續(可變長度指令)
0xac ireturn 從當前方法返回int
0xad lreturn 從當前方法返回long
0xae freturn 從當前方法返回float
0xaf dreturn 從當前方法返回do le
0xb0 areturn 從當前方法返回對象引用
0xb1 return 從當前方法返回void
0xb2 getstatic 獲取指定類的靜態域,並將其值壓入棧頂
0xb3 putstatic 爲指定的類的靜態域賦值
0xb4 getfield 獲取指定類的實例域,並將其值壓入棧頂
0xb5 putfield 爲指定的類的實例域賦值
0xb6 invokevirtual 調用實例方法
0xb7 invokespecial 調用超類構造方法,實例初始化方法,私有方法
0xb8 invokestatic 調用靜態方法
0xb9 invokeinterface 調用接口方法
0xba 無此指令
0xbb new 創建一個對象,並將其引用值壓入棧頂
0xbc newarray 創建一個指定原始類型(如int, float, char…)的數組,並將其引用值壓入棧頂
0xbd anewarray 創建一個引用型(如類,接口,數組)的數組,並將其引用值壓入棧頂
0xbe arraylength 獲得數組的長度值並壓入棧頂
0xbf athrow 將棧頂的異常拋出
0xc0 checkcast 檢驗類型轉換,檢驗未通過將拋出ClassCastException
0xc1 instanceof 檢驗對象是否是指定的類的實例,如果是將1壓入棧頂,否則將0壓入棧頂
0xc2 monitorenter 獲得對象的鎖,用於同步方法或同步塊
0xc3 monitorexit 釋放對象的鎖,用於同步方法或同步塊
0xc4 wide <待補充>
0xc5 multianewarray 創建指定類型和指定維度的多維數組(執行該指令時,操作棧中必須包含各維度的長度值),並將其引用值壓入棧頂
0xc6 ifnull 爲null時跳轉
0xc7 ifnonnull 不爲null時跳轉
0xc8 goto_w 無條件跳轉(寬索引)
0xc9 jsr_w 跳轉至指定32位offset位置,並將jsr_w下一條指令地址壓入棧頂
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章