Java字節碼是Java虛擬機執行的一種指令格式.class文件是編譯器編譯以後供虛擬機解釋執行的二進制字節碼文件.java
下面舉個例子,寫一段Java打碼,並編譯.數組
package com.xfhy.test;
public class Hello {
private int num = 1;
public int add() {
num = num + 2;
return num;
}
}
複製代碼
編譯獲得class文件以後,用Hex Fiend軟件打開該class文件.安全
CAFEBABE 00000034 00130A00 04000F09 00030010 07001107 00120100 036E756D
01000149 0100063C 696E6974 3E010003 28295601 0004436F 64650100 0F4C696E
654E756D 62657254 61626C65 01000361 64640100 03282949 01000A53 6F757263
6546696C 6501000A 48656C6C 6F2E6A61 76610C00 0700080C 00050006 01001363
6F6D2F78 6668792F 74657374 2F48656C 6C6F0100 106A6176 612F6C61 6E672F4F
626A6563 74002100 03000400 00000100 02000500 06000000 02000100 07000800
01000900 00002600 02000100 00000A2A B700012A 04B50002 B1000000 01000A00
00000A00 02000000 03000400 04000100 0B000C00 01000900 00002B00 03000100
00000F2A 2AB40002 0560B500 022AB400 02AC0000 0001000A 0000000A 00020000
0007000A 00080001 000D0000 0002000E
複製代碼
class文件內部就是長這個樣子. 裏面是一堆16進制字節,徹底看不懂.JVM是如何解讀的?markdown
class文件格式採用一種相似於C語言結構體的僞結構來存儲數據,這種僞結構只有兩種數據類型: 無符號數和表.數據結構
class的內容其實就是下面這張表裏面的數據順序排列的,只須要安裝這個順序逐一進行解讀就能夠了:ide
類型 | 名稱 | 說明 | 長度 |
---|---|---|---|
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 個字節 |
用於標記當前文件是class(爲啥不是用後綴來標記該文件爲class文件,由於防止後綴被修改,爲了安全),固定值爲0XCAFEBABE.文件一開始就是這個.oop
魔數後面的00000034
是版本號,也是4個字節,其中前2個字節表示副版本號,後2個字節表示主版本號.這裏0034對應的值是52,也就是jdk 1.8.0
post
接着是常量池相關的東西了,常量池的數量不固定,須要2個字節來表示常量池容量計數值.demo裏面是0013
,也就是19.ui
咱經過javap -verbose Hello
命令查看該class的字節碼以下(只截取了常量池部分數據):this
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#16 // com/xfhy/test/Hello.num:I
#3 = Class #17 // com/xfhy/test/Hello
#4 = Class #18 // java/lang/Object
#5 = Utf8 num
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 add
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 Hello.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = NameAndType #5:#6 // num:I
#17 = Utf8 com/xfhy/test/Hello
#18 = Utf8 java/lang/Object
複製代碼
能夠看到這裏其實序號是從1開始的,並且總共是18個??? 那爲啥class文件裏面的數值是19?由於它把第0項常量空出來了:這是爲了在於知足後面某些指向常量池的索引值的數據在特定狀況下須要表達"不引用任何一個常量池項目"的含義,這種狀況可用索引值0來表示.
首先是第一個常量0x0a
,即10.這裏的10表明的是CONSTANT_Methodref_info
,即類中方法的符號引用. 常量標誌數值的含義表:
類型 | 標誌 | 描述 |
---|---|---|
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 | 表示一個動態方法調用點 |
什麼是符號引用? 常量池主要存放兩大常量,字面量
和符號引用
.
知道了該標誌的含義,說明接下來的數據就是類中方法的符號引用的數據.可是咱們不知道這個數據到底有多長.得看下面這個表格,常量池中的17種數據類型的結構總表,才知道它的結構到底如何:
從表中查出CONSTANT_Methodref_info
的tag是10,上面已經拿到了.而後接下來的2個u2表示它的數據,在demo中的值爲: 0004 000F
0x0004
,即 4,指向常量池第 4 項的索引0x000f
,即 15,指向常量池第 15 項的索引至此,第一個常量就解讀完畢了.後面還有17個常量,就不一一解讀了,就是查字典.全部的常量都在這裏了,它們最後的解讀出來是和javap -verbose Hello
解讀出來的Constant pool
是一致的.
0A00 04000F09 00030010 07001107 00120100 036E756D
01000149 0100063C 696E6974 3E010003 28295601 0004436F 64650100 0F4C696E
654E756D 62657254 61626C65 01000361 64640100 03282949 01000A53 6F757263
6546696C 6501000A 48656C6C 6F2E6A61 76610C00 0700080C 00050006 01001363
6F6D2F78 6668792F 74657374 2F48656C 6C6F0100 106A6176 612F6C61 6E672F4F
626A6563 74
複製代碼
常量池過了就是訪問標誌了,用兩個字節來表示,其標識了類或者接口的訪問信息,好比:該 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中是0021
,爲了方便尋找,我加了~~
將該位置數據間隔開.
CAFEBABE 00000034 00130A00 04000F09 00030010 07001107 00120100 036E756D
01000149 0100063C 696E6974 3E010003 28295601 0004436F 64650100 0F4C696E
654E756D 62657254 61626C65 01000361 64640100 03282949 01000A53 6F757263
6546696C 6501000A 48656C6C 6F2E6A61 76610C00 0700080C 00050006 01001363
6F6D2F78 6668792F 74657374 2F48656C 6C6F0100 106A6176 612F6C61 6E672F4F
626A6563 74~~0021~~00 03000400 00000100 02000500 06000000 02000100 07000800
01000900 00002600 02000100 00000A2A B700012A 04B50002 B1000000 01000A00
00000A00 02000000 03000400 04000100 0B000C00 01000900 00002B00 03000100
00000F2A 2AB40002 0560B500 022AB400 02AC0000 0001000A 0000000A 00020000
0007000A 00080001 000D0000 0002000E
複製代碼
0x0021
就是0x0001
和0x0020
的並集,即就是public.
我將數據標記了一下:
CAFEBABE 00000034 00130A00 04000F09 00030010 07001107 00120100 036E756D
01000149 0100063C 696E6974 3E010003 28295601 0004436F 64650100 0F4C696E
654E756D 62657254 61626C65 01000361 64640100 03282949 01000A53 6F757263
6546696C 6501000A 48656C6C 6F2E6A61 76610C00 0700080C 00050006 01001363
6F6D2F78 6668792F 74657374 2F48656C 6C6F0100 106A6176 612F6C61 6E672F4F
626A6563 740021~~00 03000400 00~~000100 02000500 06000000 02000100 07000800
01000900 00002600 02000100 00000A2A B700012A 04B50002 B1000000 01000A00
00000A00 02000000 03000400 04000100 0B000C00 01000900 00002B00 03000100
00000F2A 2AB40002 0560B500 022AB400 02AC0000 0001000A 0000000A 00020000
0007000A 00080001 000D0000 0002000E
複製代碼
類索引的值爲0x0003
, 即爲指向常量池中第三項的索引com/xfhy/test/Hello
,這裏用到了常量池,經過類索引能夠肯定類的全限定名.
父類索引的值爲0x0004
,即爲指向常量池中第4項的索引java/lang/Object
,類都是繼承自Object的.
而後是接口計數器0x0000
,這裏沒有接口,因此是0.
這裏原本接下來是接口索引集合的,可是這裏沒有用,因此不佔數據空間.
字段表用來描述類或者接口中聲明的變量.這裏的字段包含了類級別變量以及實例變量,可是不包括方法內部聲明的局部變量.
字段表裏麪包含了如下幾個數據:
類型 | 名稱 | 含義 | 數量 |
---|---|---|---|
u2 | access_flags | 訪問標誌 | 1 |
u2 | name_index | 字段名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 屬性計數器 | 1 |
attribute_info | attributes | 屬性集合 | attributes_count |
我將數據標記了一下:
CAFEBABE 00000034 00130A00 04000F09 00030010 07001107 00120100 036E756D
01000149 0100063C 696E6974 3E010003 28295601 0004436F 64650100 0F4C696E
654E756D 62657254 61626C65 01000361 64640100 03282949 01000A53 6F757263
6546696C 6501000A 48656C6C 6F2E6A61 76610C00 0700080C 00050006 01001363
6F6D2F78 6668792F 74657374 2F48656C 6C6F0100 106A6176 612F6C61 6E672F4F
626A6563 74002100 03000400 00~~000100 02000500 060000~~00 02000100 07000800
01000900 00002600 02000100 00000A2A B700012A 04B50002 B1000000 01000A00
00000A00 02000000 03000400 04000100 0B000C00 01000900 00002B00 03000100
00000F2A 2AB40002 0560B500 022AB400 02AC0000 0001000A 0000000A 00020000
0007000A 00080001 000D0000 0002000E
複製代碼
值爲0x0001
,由於只有一個字段.
接下來將demo中的這個字段進行分析一下,首先是字段表訪問標誌,這裏的值是0x0002
,是什麼含義呢?得看下面這張表
標誌名稱 | 標誌值 | 含義 |
---|---|---|
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 |
值是0x0002
表明着private修飾符.
0x0002
,查詢上面字段訪問標誌的表格,可得字段爲private
;0x0005
, 查詢常量池中的第 5 項, 可得: num
0x0006
, 查詢常量池中的第 6 項, 可得: I
0x0000
, 即沒有任何的屬性.接下來是方法表,前面兩個字節依然用來表示方法表的容量,我將數據標記了一下:
CAFEBABE 00000034 00130A00 04000F09 00030010 07001107 00120100 036E756D
01000149 0100063C 696E6974 3E010003 28295601 0004436F 64650100 0F4C696E
654E756D 62657254 61626C65 01000361 64640100 03282949 01000A53 6F757263
6546696C 6501000A 48656C6C 6F2E6A61 76610C00 0700080C 00050006 01001363
6F6D2F78 6668792F 74657374 2F48656C 6C6F0100 106A6176 612F6C61 6E672F4F
626A6563 74002100 03000400 00000100 02000500 060000~~00 02000100 07000800
010009~~00 00002600 02000100 00000A2A B700012A 04B50002 B1000000 01000A00
00000A00 02000000 03000400 04000100 0B000C00 01000900 00002B00 03000100
00000F2A 2AB40002 0560B500 022AB400 02AC0000 0001000A 0000000A 00020000
0007000A 00080001 000D0000 0002000E
複製代碼
方法表的容量爲0x0002
,即demo中有2個方法(還有1個默認的構造方法,別忘了..).
既然是表,那確定有結構,還有嚴格的順序規定.
類型 | 名稱 | 含義 | 數量 |
---|---|---|---|
u2 | access_flags | 訪問標誌 | 1 |
u2 | name_index | 方法名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 屬性計數器 | 1 |
attribute_info | attributes | 屬性集合 | attributes_count |
方法也是有訪問標誌的
標誌名稱 | 標誌值 | 含義 |
---|---|---|
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 | 方法是不是有編譯器自動產生的 |
第一個方法是:
000100 07000800 010009
複製代碼
0x0001
,查詢上面字段訪問標誌的表格,可得字段爲 public;0x0007
,查詢常量池中的第 7 項,可得:<init>
<init>
的方法實際上就是默認的構造方法
了。0x0008
,查詢常量池中的第 8 項,可得:()V
0x0001
,即這個方法表有一個屬性。0x0009
, 查下常量池中的第 9 項:Code
.即這是一個Code
屬性,咱們方法裏面的代碼就是存放在這個 Code 屬性裏面。相關細節暫且不表。下一節會詳細介紹 Code 屬性。先跳過屬性表,咱們再來看下第二個方法: 000100 0B000C00 010009
0x0001
,查詢上面字段訪問標誌的表格,可得字段爲 public;0x000b
,查詢常量池中的第 11 項,可得: add
0x000c
,查詢常量池中的第 12 項,可得: ()I
0x0001
,即這個方法表有一個屬性。0x0009
,即這是一個 Code 屬性。 能夠看到,第二個方法表就是咱們自定義的add()
方法了。上面提到了屬性表,如今咱們來看一下屬性表是什麼.
屬性表實際上有不少類型,上面看到的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屬性表結構:
類型 | 名稱 | 數量 | 含義 |
---|---|---|---|
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屬性了,就是查字典.
咱們能夠一步到位,使用javap -verbose Hello
能夠獲得獲得字節碼指令
public com.xfhy.test.Hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field num:I
9: return
LineNumberTable:
line 3: 0
line 4: 4
public int add();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: aload_0
2: getfield #2 // Field num:I
5: iconst_2
6: iadd
7: putfield #2 // Field num:I
10: aload_0
11: getfield #2 // Field num:I
14: ireturn
LineNumberTable:
line 7: 0
line 8: 10
複製代碼
這裏涉及到一些字節碼指令,這些指令含義以下表:
字節碼 | 助記符 | 指令含義 |
---|---|---|
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 下一條指令地址壓入棧頂 |
固然,這麼多屬性根本不用記住,須要的時候查表就行.
SourceFile 屬性,即附加屬性.它的屬性結構以下
類型 | 名稱 | 數量 | 含義 |
---|---|---|---|
u2 | attribute_name_index | 1 | 屬性名索引 |
u4 | attribute_length | 1 | 屬性長度 |
u2 | sourcefile_index | 1 | 源碼文件索引 |
0x000d
,即常量池中的第 13 項,查詢可得: SourceFile
0x00 00 00 02
, 即長度爲 20x000e
, 即常量池中的第 14 項, 查詢可得: Hello.java
其餘還有一些Java虛擬機預約義了不少屬性,就不一一解讀了,就是查字典.
Java字節碼就是一些Java虛擬機的指令,而這些指令須要依賴class文件,因此首先得讀取class文件內容.而class文件內容就是一些16進制的數據,很緊湊地將數據按順序擺放在一塊兒,只須要順序解讀,便可獲得指令內容.
❝ps: 就像《深刻理解Java虛擬機》一書中所說的那樣,解讀class其實就是查字典嘛,來嘛,查嘛,慢慢搞嘛,我就不行治不了你. 剛開始的時候讀起來特別不舒服,讀不太懂這玩意兒,後面慢慢地終於讀懂了,再寫篇文章詳細記錄一下,加深現象. 博客寫得不是很詳細,若是感興趣,建議仍是看書(《深刻理解Java虛擬機》第六章)更系統些.
❞