鑑於十進制的計算機還遙遙無期,我們目前的計算機都是二進制的計算機,而二進制的計算機僅能識別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
,是0x0020
和0x0001
的並集,即這是一個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
,即沒有任何的屬性。
確認過眼神,我遇上對的了。
至此,字段表解讀完成。
注意事項
- 字段表集合中不會列出從父類或者父接口中繼承而來的字段。
- 內部類中爲了保持對外部類的訪問性,會自動添加指向外部類實例的字段。
- 在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屬性。
先跳過屬性表,我們再來看下第二個方法:
訪問標誌的值爲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下一條指令地址壓入棧頂 |