Android開發(fā)者功能開發(fā)幾乎都是面向Java/Kotlin語法編程咱揍,對于class文件的關注相對較少刀崖。 當你反編譯class文件或程序編譯期修改字節(jié)碼亦做代碼注入時惊科,讀懂字節(jié)碼成為一道繞不開的檻。
文章主要描述如何快速讀懂一個class文件亮钦。涉及到的 JVM 指令及字節(jié)碼結構已做了整理馆截,這部分知識平時用到的時候查一下便可,用多了自然記住了蜂莉。即使你是一個新手蜡娶,按照下面的思路整合,你也可以從 0 上手映穗。
讀完本篇文章你會收獲:
- Class 文件結構長啥樣
- JVM 操作指令有哪些
- 如何從二進制流中讀懂 Class 文件
舉個栗子?? 帶你入門
編寫一個簡單的java文件 窖张。
使用javac編譯 TestClass.java 輸出 TestClass.Class,得到的二進制流文件蚁滋∷藿樱可以通過工具查看其內(nèi)容,在 MAC 平臺上推薦使用 iHex-Hex Editor 以十六進制格式查看辕录,大概長這個樣子睦霎。
這看起來像天書,無從下手走诞。實際上任何編程產(chǎn)物最終都會演化成二進制碎赢,它必定是按照某種規(guī)則來申明對應邏輯。 Java 虛擬機為了能夠解析這個文件速梗,要求其內(nèi)容必須嚴格按照格式來排版,這種結構格式便是Class文件結構襟齿。但是單單知道里面有哪些內(nèi)容還不夠姻锁,虛擬機還需要一套規(guī)則來操作這些內(nèi)容,這些規(guī)則便是字節(jié)碼操作指令猜欺。
要讀懂這些天書位隶,先得了解天書是怎么寫出來的。
Class文件結構長啥樣
java文件經(jīng)過 JAVA編譯器(javac)編譯成中間代碼字節(jié)碼class文件开皿。class文件 是一個二進制文件涧黄,里面的內(nèi)容已經(jīng)是嚴格按照 Class 文件結構規(guī)則排列的篮昧。
下面表示是 Class 文件結構表,依次按照行從上到下解析笋妥,也就是說文件開頭優(yōu)先解析 magic(魔數(shù))懊昨。
類型 | 名稱 | 描述 | 數(shù)量 |
---|---|---|---|
u4(4個字節(jié)) | magic | 確定該文件是否為一個能被虛擬機接受的Class文件,類似于ID | 1 |
u2(2個字節(jié)) | minot_version | 次版本號 | 1 |
u2(2個字節(jié)) | mahor_version | 主版本號 | 1 |
u2(2個字節(jié)) | constant_pool_count | 常量池容量計數(shù)值春宣,從1開始計算酵颁,0則表示不引用任何一個常量池項目 | 1 |
cp_info | constant_pool | 常量池 | constant_pool_count-1 |
u2(2個字節(jié)) | access_flags | 訪問標志 | 1 |
u2(2個字節(jié)) | this_class | 類索引 | 1 |
u2(2個字節(jié)) | super_class | 父類索引 | 1 |
u2(2個字節(jié)) | interfaces_count | 實現(xiàn)接口的數(shù)目 | 1 |
u2(4個字節(jié)) | interfaces | 接口索引 | interfaces_count |
u2(4個字節(jié)) | fields_count | 字段的數(shù)目 | 1 |
field_info | fields | 字段內(nèi)容 | fields_count |
u2(2個字節(jié)) | methods_count | 方法的數(shù)目 | 1 |
method_info | methods | 方法內(nèi)容 | methods_count |
u2(2個字節(jié)) | attributes_count | 屬性的數(shù)目 | 1 |
attribute_info | attributes | 屬性內(nèi)容 | attributes_count |
單靠上面表還不夠,描述列中部分內(nèi)容包含字節(jié)碼層面的描述月帝,還需根據(jù)特定表格進行查詢解析躏惋,具體如下:
- 常量池對應常量表約束
- 訪問標志對應訪問標志表約束
- 字段對應字段表約束
- 方法對應方法表約束
- 屬性對應屬性表約束,同時屬性內(nèi)可能還需要進一步劃分嚷辅,對應Code屬性結構, 異常屬性結構等表約束
- 還有一些特殊字符串格式約束簿姨,比如特殊字符串表等等
常量表
常量池主要存放兩種類型
- 字面量,包含文本字符串簸搞,final 常量值等扁位。
- 符號引用,類和接口的全限定名攘乒,字段的名稱和描述符贤牛,方法的名稱和描述符。
Class文件只保存各個方法则酝,字段信息殉簸,不保存內(nèi)存信息。只有經(jīng)過運行期轉(zhuǎn)換才能得到真正的內(nèi)存入口沽讹。當虛擬機運行時般卑,需要從常量池中獲取到對應的符號引用,再經(jīng)過類創(chuàng)建者運行時解析爽雄,得到具體的內(nèi)存地址蝠检。
類型 | 子結構 | 標志 | 描述 |
---|---|---|---|
CONSTANT_Utf8_info | tag | u1 = 1 | UTF-8編碼的字符串 |
- | lenght | u2 | UTF-8編碼的字符串占用的字節(jié)數(shù) |
- | bytes | u1 | 長度為lenght的UTF-8編碼的字符串 |
CONSTANT_Integer_info | tag | u1=3 | 整型字面量 |
- | bytes | u4 | 按照高位在前存儲的int值 |
CONSTANT_Float_info | tag | u1=4 | 浮點型字面量 |
- | bytes | u4 | 按照高位在前存儲的float值 |
CONSTANT_Long_info | tag | u1=5 | 長整型字面量 |
- | bytes | u8 | 按照高位在前存儲的long值 |
CONSTANT_Double_info | tag | u1=6 | 雙精度浮點型字面量 |
- | bytes | u8 | 按照高位在前存儲的double值 |
CONSTANT_Class_info | tag | u1=7 | 類或接口的符號引用 |
- | bytes | u2 | 指向全限定名常量項的索引 |
CONSTANT_String_info | tag | u1=8 | 字符串類型字面量 |
- | bytes | u2 | 指向字符串字面量的索引 |
CONSTANT_Fieldref_info | tag | u1=9 | 字段的符號引用 |
- | index | u2 | 指向聲明字段的類或者接口描述符 CONSTANT_Class_info 的索引項 |
- | index | u2 | 指向聲明字段的類或者接口描述符CONSTANT_NameAndType_info 的索引項 |
CONSTANT_Methodred_info | tag | u1=10 | 類中方法的符號引用 |
- | index | u2 | 指向聲明字段的類或者接口描述符 CONSTANT_Class_info 的索引項 |
- | index | u2 | 指向聲明字段的類或者接口描述符CONSTANT_NameAndType_info 的索引項 |
CONSTANT_InterfaceMethodref_info | tag | u1=11 | 接口中方法的符號引用 |
- | index | u2 | 指向聲明字段的類或者接口描述符 CONSTANT_Class_info 的索引項 |
- | index | u2 | 指向聲明字段的類或者接口描述符CONSTANT_NameAndType_info 的索引項 |
CONSTANT_NameAndType_info | tag | u1=12 | 字段或方法的部分符號引用 |
- | index | u2 | 指向該字段或方法名稱常量項的索引 |
- | index | u2 | 指向該字段或方法名稱常量項的索引 |
CONSTANT_MethodHandle_info | tag | u1=15 | 表示方法句柄 |
- | reference_kind | u1 | 值必須在[1,9]中挚瘟,它決定了方法句柄的類型叹谁。方法句柄類型的值表示方法句柄的字節(jié)碼行為 |
- | reference_index | u2 | 值必須是對常量池的有效索引 |
CONSTANT_MethodType_info | tag | u1=16 | 識別方法類型 |
- | descriptor_index | u2 | 值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構乘盖,表示方法的描述符 |
CONSTANT_InvokeDynamic_info | tag | u1=18 | 表示一個動態(tài)方法調(diào)用點 |
- | bootstrap_method_attar_index | u2 | 值必須是對當前Class文件中引導方法表的 bootstrap_methods[]數(shù)組的有效索引 |
- | name_and_type_index | u2 | 值必須是對當前常量池的有效索引焰檩,常量池在該索引處的值必須是CONSTANT_NameAndType_info結構,表示方法名和方法描述符 |
訪問標志表
針對類订框,字段表,方法表中的訪問標志進行劃分。
-
類訪問標志衩侥,用于識別一些類或者接口層次的訪問信息国旷, 包括這個 Class 是類還是接口,是否被定義成 public 類型茫死,是否被定義成 abstract 類類型跪但,如果是類的話,是否被聲明為 final 等璧榄。
標志名稱 標志值 描述 ACC_PUBLIC 0x0001 是否為public類型 ACC_FINAL 0x0010 是否被聲明為final特漩,只有類可設置 ACC_SUPER 0x0020 是否允許使用invokespecial字節(jié)碼指令的新語意,invokespecial指令的語意在JDK1.0.2發(fā)生過變化骨杂,為了區(qū)別這條指令使用哪種語意涂身,JDK1.0.2之后編譯出來的類的這個標識必須都為真 ACC_INTERFACE 0x0200 標識這個是一個接口 ACC_ABSTRACT 0x0400 是否為abstract類型,對于接口或者抽象類來說搓蚪,此標志的值都為真蛤售,其他類型為假 ACC_SYNTHETIC 0x1000 標識這個類并非由用戶代碼產(chǎn)生的 ACC_ANNOTATION 0x2000 標識這是一個注解 ACC_ENUM 0x4000 標識這是一個枚舉 -
內(nèi)部類訪問標志
標志名稱 標志值 描述 ACC_PUBLIC 0x0001 內(nèi)部類是否為public ACC_PRIVATE 0x0002 內(nèi)部類是否為private ACC_PROTECTED 0x0004 內(nèi)部類是否為protected ACC_STATIC 0x0008 內(nèi)部類是否為protected ACC_FINAL 0x0010 內(nèi)部類是否為protected ACC_INTERFACE 0x0020 內(nèi)部類是否為接口 ACC_ABSTRACT 0x0400 內(nèi)部類是否為abstract ACC_SYNTHETIC 0x1000 內(nèi)部類是否并非由用戶代碼產(chǎn)生 ACC_ANNOTATION 0x2000 內(nèi)部類是否是一個注解 ACC_ENUM 0x4000 內(nèi)部類是否是一個枚舉 -
字段訪問標志
標志名稱 標志值 描述 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_TRANSIENT 0x0080 字段是否為transient ACC_SYNTHETIC 0x1000 字段是否由編譯器自動產(chǎn)生的 ACC_ENUM 0x4000 字段是否為enum -
方法訪問標志
標志名稱 標志值 描述 ACC_PUBLIC 0x0001 方法是否為public ACC_PRIVATE 0x0002 方法是否為private ACC_PROTECTED 0x0004 方法是否為protected ACC_STATIC 0x0008 方法是否為static ACC_FINAL 0x0010 方法是否為final ACC_SYNCHRONIZED 0x0020 方法是否為synchronized ACC_BRIDGE 0x0040 方法是否由編譯器產(chǎn)生的橋接方法 ACC_VARARGS 0x0080 方法是否接受不定參數(shù) ACC_NATIVE 0x0100 方法是否為native ACC_ABSTRACT 0x0400 方法是否為abstract ACC_STRICTFP 0x0800 方法是否為strictfp ACC_SYNTHETIC 0x1000 方法是否由編譯器自動產(chǎn)生的
字段表
用于描述接口和類中聲明的變量,包括類級別變量以及實例級別變量妒潭。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
u2 | attributes | attributes_count |
其中 access_flags 見上面訪問標志表中的字段訪問標志悴能。
方法表
方法表包含訪問標志,名稱索引和描述符索引雳灾,屬性信息等幾項漠酿。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
其中方法的 access_flags 見上述的方法訪問標志。
屬性表
屬性表用于解釋Class文件中字段表谎亩,方法表中攜帶的屬性表集合炒嘲,用于描述某些場景專有的信息。
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節(jié)碼指令 |
ConstantValue | 字段表 | final關鍵字定義的常量值 |
Deprecated | 類匈庭,方法表夫凸,字段表 | final關鍵字定義的常量值 |
Exceptions | 方法表 | final方法拋出的異常 |
EnclosingMethod | 類文件 | 僅當一個類為局部類或者匿名類時才能擁有這個屬性,這個屬性用于標識這個類所在的外圍方法 |
InnerClasses | 類文件 | 內(nèi)部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節(jié)碼指令的對應關系 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
StackMapTable | Code屬性 | JDK1.6中新增的屬性阱持,供新的類型檢查校驗器(Type Checker)檢查和處理目標方法的局部變量和操作數(shù)棧鎖需要的類型是否匹配 |
Signature | 類夭拌,方法表,字段表 | JDK1.5中新增的屬性衷咽,這個屬性用于支持泛型情況下的方法簽名鸽扁,在java語言中,任何類镶骗,接口桶现,初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或者參數(shù)化類型(Parameterized Types),則Signature屬性會為它記錄泛型簽名信息卖词。由于java的泛型采用擦除法實現(xiàn),在為了類型信息被擦除后導致簽名混亂,需要這個屬性記錄泛型中的相關信息 |
SourceFile | 類文件 | 記錄源文件名稱 |
SourceDebugExtension | 類文件 | JDK1.6中新增的屬性此蜈,SourceDebugExtension屬性用于存儲額外的調(diào)試信息即横。譬如在進行JSP文件調(diào)試時,無法通過Java堆棧來定位到JSP文件的行號裆赵,JSR-45規(guī)范為這些非Java語言編寫东囚,卻需要編譯成字節(jié)碼并運行在Java虛擬機中的程序提供了一個進行調(diào)試的標準機制,使用SourceDebugExtension屬性就可以用于存儲這個標準所新加入的調(diào)試信息 |
Synthetic | 類战授,方法表页藻,字段表 | 標識方法或者字段是否為編譯器自動生成的 |
LocalVariableTypeTable | 類 | JDK1.5中新增的屬性,它使用特征簽名代替描述符植兰,是為了引入泛型語法之后能描述泛型參數(shù)化類型而添加的 |
RuntimevisibleAnnotations | 類份帐,方法表牌芋,字段表 | JDK1.5中新增的屬性刁赖,為動態(tài)注解提供支持宴树。RuntimevisibleAnnotations 屬性用于指明哪些注解是運行時(實際上運行時就是進行反射調(diào)用)可見的 |
RuntimeInvisibleAnnotations | 類轰驳,方法表徊都,字段表 | JDK1.5中新增的屬性叉橱,與 RuntimevisibleAnnotations 屬性作用剛好相反曹锨, 用于指明哪些注解是運行時不可見的 |
RuntimeVisibleParameterAnnotations | 方法表 | JDK1.5中新增的屬性舵盈,作用與 RuntimevisibleAnnotations 屬性類似毡咏,只不過作用對象為方法參數(shù) |
RuntimeInvisibleParameterAnnotations | 方法表 | JDK1.5中新增的屬性驮宴,作用與 RuntimeInvisibleAnnotations 屬性類似,只不過作用對象為方法參數(shù) |
AnnotationDetault | 方法表 | JDK1.5中新增的屬性呕缭,用于記錄注解類元素的默認值 |
BootstrapMethods | 類文件 | JDK1.5中新增的屬性堵泽,用于保存 invokedynamic 指令引用的引導方法限定符 |
上述的每一個屬性都需要從常量池中引用一個CONSTANT_Utf8_info類型常量來標示。還包含attribute_length(u4) 用于標示屬性值所占用的位數(shù)臊旭,后面再跟著屬性內(nèi)容落恼。下面為一些常見的屬性子表結構。
- Code屬性結構表离熏,用于描述代碼塊
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_locals | 1 |
u4 | code_length | 1 |
u1 | code | code_lenght |
u2 | exception_table_lenght | 1 |
exception_info | exception_table | exception_table_length |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
- 異常屬性結構表佳谦,用于描述異常信息
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | start_pc | 1 |
u2 | end_pc | 1 |
u2 | handler_pc | 1 |
u2 | catch_type | 1 |
- Exceptions屬性結構表
區(qū)別與異常表,該表主要是列舉中方法中可能拋出的受檢查異常滋戳,也就是方法描述時 throws關鍵字列舉的異常钻蔑。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_exceptions | 1 |
u2 | exception_index_table | number_of_exceptions |
- LineNumberTable屬性結構表
用于描述 Java 源碼行號與字節(jié)碼行號之間的對應關系,默認聲稱到 Class 文件中奸鸯。
類型 | 名稱 | 數(shù)量 |
---|---|---|
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 包含 start_pc 和 line_number 兩個 u2 類型的數(shù)據(jù)項咪笑。
- LocalVariableTable屬性結構表
用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關系,默認生成到Class文件中娄涩。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | local_variable_table_lenght | 1 |
local_variable_info | local_variable_table | local_variable_table_lenght |
其中 local_variable_info 是代表棧幀與源碼中局部變量的關聯(lián)窗怒,見下表:
類型 | 名稱 | 含義 | 數(shù)量 |
---|---|---|---|
u2 | start_pc | 局部變量的生命周期開始的字節(jié)碼偏移量 | 1 |
u2 | length | 局部變量的生命周期開始的作用范圍覆蓋長度 | 1 |
u2 | name_index | 指向常量池 CONSTANT_Utf8_info 索引 | 1 |
u2 | descriptor_index | 指向常量池 CONSTANT_Utf8_info 索引 | 1 |
u2 | index | 局部變量在棧幀局部變量表中Slot的位置 | 1 |
- SourceFile屬性結構表
用于記錄生成這個 Class文件 的源碼文件名稱映跟。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | sourcefile_index | 1 |
其中 sourcefile_index 為指向常量池 CONSTANT_Utf8_info 索引。
- ConstantValue屬性結構表
用于通知虛擬機自動為靜態(tài)變量賦值扬虚。只有被 static 關鍵字修飾的變量才可以使用這項屬性努隙。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | constant_index | 1 |
- InnerClasses屬性結構表
用于記錄內(nèi)部類與宿主類之間的關聯(lián),如果一個類中定義了內(nèi)部類辜昵,編譯器則會為它生成內(nèi)部類 InnerClasses 屬性荸镊。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_classes | 1 |
inner_classes_info | inner_classes | number_of_classes |
每一個 inner_classes_info 代表一個內(nèi)部類信息,結構如下:
類型 | 名稱 | 含義 | 數(shù)量 |
---|---|---|---|
u2 | inner_class_info_index | 指向常量池 CONSTANT_Class_info 索引 | 1 |
u2 | outer_class_info_index | 指向常量池 CONSTANT_Class_info 索引 | 1 |
u2 | inner_name_index | 指向常量池 CONSTANT_Utf8_info 索引堪置,代表這個內(nèi)部類的名稱躬存,如果匿名則為0 | 1 |
u2 | inner_class_access_flags | 內(nèi)部類的訪問標志,見上述訪問標志篇章 | 1 |
- Deprecated/Synthetic屬性結構表
前者是用于標示某個類舀锨,字段或者方法是否不再推薦使用岭洲。
后者是用于標示字段或者方法不是由 Java 源碼直接產(chǎn)生,所有由非用戶代碼生成的方法都需要設置 Synthetic屬性 或者 ACC_SYNTHETIC標志雁竞,但是 init方法 和 clinit方法 除外钦椭。結構如下:
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
- StackMapTable屬性結構表
于 JDK1.6 之后添加在 Class 規(guī)范中,位于 Code屬性 表中碑诉,該屬性會在虛擬機類加載的字節(jié)碼校驗階段被新類型檢查檢驗器(Type Checker)使用彪腔。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_entries | 1 |
stack_map_frame | stack_map_frame_entries | number_of_entries |
- Signature屬性結構表
于 JDK1.5 發(fā)布之后添加到 Class 規(guī)范中,它是一個可選的定長屬性进栽,可以出現(xiàn)在類德挣,屬性表,方法表結構的屬性表中快毛。該屬性會記錄泛型簽名信息格嗅,在 Java 語言中泛型采用的是擦除法實現(xiàn)的偽泛型,在字節(jié)碼(Code屬性)中唠帝,泛型信息編譯之后都統(tǒng)統(tǒng)被擦除掉屯掖。由于無法像 C# 等運行時支持獲取真泛型類型,添加該屬性用于彌補該缺陷绍坝,現(xiàn)在 Java 反射已經(jīng)能獲取到泛型類型轩褐。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | signature_index | 1 |
其中 signature_index 值必須是一個對常量池的有效索引且為 CONSTANT_Utf8_info,表示類簽名玖详,方法類型簽名或字段類型簽名把介。如果當前 Signature 屬性是類文件的屬性勤讽,則這個結構表示類簽名,如果當前 Signature 屬性是方法表的屬性拗踢,則表示方法類型簽名地技,如果當前 Signature 屬性是字段表的屬性,則表示字段類型簽名秒拔。
- BootstrapMethods屬性結構表
于 JDK1.7 發(fā)布后添加到 Class 規(guī)范中,是一個復雜變長的屬性飒硅,位于類文件的屬性表中。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | num_bootstrap_methods | 1 |
bootstrap_method | bootstrap_methods | num_bootstrap_methods |
其中 bootstrap_method 結構如下:
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | bootstrap_method_ref | 1 |
u2 | num_bootstrap_arguments | 1 |
u2 | bootstrap_arguments | num_bootstrap_arguments |
特殊字符串
所謂全限定名会前,就是使用 "." 分割類全名。比如 com/yummylau/TestClass 把類全名的 "." 換成 "/"反璃,變成 com.yummylau.TestClass梧田,多個全限定名可使用多個 "未状;"分割。
而簡單名稱則沒有類型和參數(shù)修飾的方法或者字段的名字,比如方法 inc() 和字段 m 分別標示為 inc 和 m 。特殊字符串表包含一些基礎類型的描述及方法描述。如下:
標識字符 | 含義 |
---|---|
B | 基本類型 byte |
C | 基本類型 char |
D | 基本類型 double |
F | 基本類型 float |
I | 基本類型 int |
J | 基本類型 long |
S | 基本類型 short |
Z | 基本類型 boolean |
V | 基本類型 void |
L | 對象類型,比如 Ljava/lang/Object |
- 針對數(shù)組,每一個維度使用一個前置的 [ 字符來描述信粮。比如定義一個 java.lang.String[][] 數(shù)組,被記錄為 [[java.lang.String,一個整型數(shù)組 int[] 被記錄為[I
- 針對方法,使用以下描述符:
方法場景 | 描述符 |
---|---|
void inc() | ()V |
java.lang.String toString() | ()Ljava/lang/String; |
int indexOf(char[]source鲫尊,int sourceOffest,int sourceCount,char[] target,int targetOffset,int targetCOunt系任,int formIndex) | ([CII[CIII)I |
表格比較多霜旧,但是一般在使用的過程中逐步查找就可以了崎逃。
重新認識字節(jié)天書
這份字節(jié)天書巴柿,我按照上述的class文件結構表字節(jié)區(qū)間規(guī)律重新進行了排版袁波。
行從上到下一一對應class文件結構表中的行。下面開始解析每行字節(jié)的含義,基本的邏輯都是索引到某個數(shù)據(jù)結構,該數(shù)據(jù)結構對應上述的某一張表格界赔。
第1行為 魔數(shù)
袜腥,主要是用于確認這個文件是否能被虛擬機加載, CAFEBABE 其實就是 Baristas咖啡。
第2行為 主次版本號
,從 java-class版本對應 可知表示 Java SE 10,向下兼容到 JDK1.1。
第3行為 常量池中常量數(shù)量
,由于從 1 開始計數(shù),第 0 項預留用于表示“不引用任何一個常量池項目”, 轉(zhuǎn)化 16 進制之后可知常量池有 18 項常量。 每一項常量都對應常量表的某一項惯吕, 按照表中規(guī)定的每一項常量對應各自的結構钳宪。
第4行為 第1項常量
半醉,為類中方法的符號引用衬吆,格式為第4項常量.第15項常量冒嫡,為 java/lang/Object.< init >:()V
第5行為 第2項常量
,為字段的符號引用,格式為第3項常量.第16項常量,為 TestClass.m:I
第6行為 第3項常量
,為類或接口的符號引用色徘,指向第 17 項常量,為 TestClass
第7行為 第4項常量
,為類或接口的符號引用,指向第 18 項常量,為 java/lang/Object
第8行為 第5項常量
翎冲,為 UTF-8 編碼的字符串,長度為 1, 轉(zhuǎn)化得到 m
第9行為 第6項常量
,為 UTF-8 編碼的字符串稍坯,長度為 1,轉(zhuǎn)化得到 I
第10行為 第7項常量
,為 UTF-8 編碼的字符串,長度為 6辨液,轉(zhuǎn)化得到 < init >
第11行為 第8項常量
,為 UTF-8 編碼的字符串,長度為 3,轉(zhuǎn)化得到 ()V
第12行為 第9項常量
,為 UTF-8 編碼的字符串,長度為 4,轉(zhuǎn)化得到 Code
第13行為 第10項常量
蚂踊,為 UTF-8 編碼的字符串涝动,長度為 15,轉(zhuǎn)化得到 LineNumberTable
第14行為 第11項常量
育苟,為 UTF-8 編碼的字符串漱竖,長度為 3,轉(zhuǎn)化得到 inc
第15行為 第12項常量
,為 UTF-8 編碼的字符串,長度為 3,轉(zhuǎn)化得到 ()I
第16行為 第13項常量
,為 UTF-8 編碼的字符串,長度為 14凳谦,轉(zhuǎn)化得到 SourceFile
第17行為 第14項常量
绊诲,為 UTF-8 編碼的字符串板惑,長度為 10裆馒,轉(zhuǎn)化得到 TestClass.java
第18行為 第15項常量
梗搅,為字段或方法的部分符號引用, 格式為 {第7項常量}:{第8項常量}哆键,為 < init >:()V
第19行為 第16項常量
,為字段或方法的部分符號引用辫红,格式為 {第5項常量}:{第6項常量}澎胡,為 m:I
第20行為 第17項常量
,為 UTF-8 編碼的字符串垦搬,長度為9,轉(zhuǎn)化得到 TestClass
第21行為 第18項常量
艳汽,為 UTF-8 編碼的字符串猴贰,長度為16义郑,轉(zhuǎn)化得到 java/lang/Object
第22行為 訪問標志
允华, 查看訪問標志表 可知為 0x0021(0x0001|0x0020)表明這個是一個普通類德澈,既不是接口,枚舉也不是注解,被 public 關鍵字修飾但沒有被聲明為 final 或 abstract
第23行為 類索引
适袜,對應 第3項常量
蔓同,為 TestClass
第24行為 父類索引
娜庇,對應 第4項常量
惠猿,為 java/lang/Object
第25行為 實現(xiàn)接口的數(shù)目
匣砖,0 表示沒有實現(xiàn)任何接口
第26行為 字段的數(shù)目
肌厨,存在一個字段需要解析
第27行 為 第1個字段
绳姨,查看字段表可知 訪問標志(0002)為 private, 名字(0005)為 m, 描述(0006)為 I, 沒有屬性(0000)
第28行 為 方法的數(shù)目
叶眉,存在兩個方法需要解析
第29~32行 為 第1個方法
吩案,查看方法表可知 訪問標志(0001)為 public, 名字(0007)為 < init >, 描述(0008)為 ()V, 一個屬性(0001)。由于存在一個屬性,繼續(xù)查看屬性表薪缆。從 30 行開始解析屬性 0009 解析為 第9項常量
Code, 查看屬性表-Code屬性結構表惠遏。但是發(fā)現(xiàn) Code 塊內(nèi)部分字節(jié)難以解析汁汗。這是因為部分還需要結合 JVM 操作字節(jié)碼指令才可以。這里先 mark 暫停。
第33~36行 為 第2個方法
,查看方法表可知 訪問標志(0001)為 public, 名字(000B)為 inc, 描述(000C)為 ()I, 一個屬性(0001)双戳。也存在一個屬性山憨。從 34 行開始解析屬性 0009 解析為 第9項常量
Code, 同上,mark 暫停弥喉。
第37行為 屬性的數(shù)目
郁竟,存在一個屬性
第38行為 第1個屬性
,000D解析為 第13項常量
sourceFile, 解析 sourceFile屬性得到 TestClass.java
經(jīng)過上述的解析由境,我們可得到:
到此棚亩,基本讀懂了這份字節(jié)天書。 但是 Code 屬性內(nèi)容還是缺失虏杰。這時候我們需要一份字節(jié)碼指令總表來幫助我們進一步解析 Code 里面涉及哪些指令及信息讥蟆。
JVM 操作指令有哪些
下面是 JVM 操作指令表
字節(jié)碼 | 助記符 | 指令含義 |
---|---|---|
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 | 將 double 型 0 推送至棧頂 |
0x0f | dconst_1 | 將 double 型 1 推送至棧頂 |
0x10 | bipush | 將單字節(jié)的常量(-128 - 127)推送至棧頂 |
0x11 | sipush | 將一個短整形常量常量(-32768 - 32767)推送至棧頂 |
0x12 | ldc | 將 int, float, String 型常量值從常量池中推送至棧頂 |
0x13 | ldc_w | 將 int, float, String 型常量值從常量池中推送至棧頂(寬索引) |
0x14 | ldc2_w | 將 long 或 float 型常量值從常量池中推送至棧頂(寬索引) |
0x15 | iload | 將指定的 int 型本地變量推送至棧頂 |
0x16 | lload | 將指定的 long 型本地變量推送至棧頂 |
0x17 | fload | 將指定的 float 型本地變量推送至棧頂 |
0x18 | dload | 將指定的 dload 型本地變量推送至棧頂 |
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 | 將第一個 double 型本地變量推送至棧頂 |
0x27 | dload_1 | 將第二個 double 型本地變量推送至棧頂 |
0x28 | dload_2 | 將第三個 double 型本地變量推送至棧頂 |
0x29 | dload_3 | 將第四個 double 型本地變量推送至棧頂 |
0x2a | aload_0 | 將第一個引用類型本地變量推送至棧頂 |
0x2b | aload_1 | 將第二個引用類型本地變量推送至棧頂 |
0x2c | aload_2 | 將第三個引用類型本地變量推送至棧頂 |
0x2d | aload_3 | 將第四個引用類型本地變量推送至棧頂 |
0x2e | iaload | 將 int 型數(shù)組指定索引的值推送至棧頂 |
0x2f | laload | 將 long 型數(shù)組指定索引的值推送至棧頂 |
0x30 | faload | 將 float 型數(shù)組指定索引的值推送至棧頂 |
0x31 | daload | 將 double 型數(shù)組指定索引的值推送至棧頂 |
0x32 | aaload | 將引用型數(shù)組指定索引的值推送至棧頂 |
0x33 | baload | 將 boolean 或 byte 型數(shù)組指定索引的值推送至棧頂 |
0x34 | caload | 將 char 型數(shù)組指定索引的值推送至棧頂 |
0x35 | saload | 將 short 型數(shù)組指定索引的值推送至棧頂 |
0x36 | istore | 將棧頂 int 型數(shù)值存入指定本地變量 |
0x37 | lstore | 將棧頂 long 型數(shù)值存入指定本地變量 |
0x38 | fstore | 將棧頂 float 型數(shù)值存入指定本地變量 |
0x39 | dstore | 將棧頂 double 型數(shù)值存入指定本地變量 |
0x3a | astore | 將棧頂引用型數(shù)值存入指定本地變量 |
0x3b | istore_0 | 將棧頂 int 型數(shù)值存入第一個本地變量 |
0x3c | istore_1 | 將棧頂 int 型數(shù)值存入第二個本地變量 |
0x3d | istore_2 | 將棧頂 int 型數(shù)值存入第三個本地變量 |
0x3e | istore_3 | 將棧頂 int 型數(shù)值存入第四個本地變量 |
0x3f | lstore_0 | 將棧頂 long 型數(shù)值存入第一個本地變量 |
0x40 | lstore_1 | 將棧頂 long 型數(shù)值存入第二個本地變量 |
0x41 | lstore_2 | 將棧頂 long 型數(shù)值存入第三個本地變量 |
0x42 | lstore_3 | 將棧頂 long 型數(shù)值存入第四個本地變量 |
0x43 | fstore_0 | 將棧頂 float 型數(shù)值存入第一個本地變量 |
0x44 | fstore_1 | 將棧頂 float 型數(shù)值存入第二個本地變量 |
0x45 | fstore_2 | 將棧頂 float 型數(shù)值存入第三個本地變量 |
0x46 | fstore_3 | 將棧頂 float 型數(shù)值存入第四個本地變量 |
0x47 | dstore_0 | 將棧頂 double 型數(shù)值存入第一個本地變量 |
0x48 | dstore_1 | 將棧頂 double 型數(shù)值存入第二個本地變量 |
0x49 | dstore_2 | 將棧頂 double 型數(shù)值存入第三個本地變量 |
0x4a | dstore_3 | 將棧頂 double 型數(shù)值存入第四個本地變量 |
0x4b | astore_0 | 將棧頂引用型數(shù)值存入第一個本地變量 |
0x4c | astore_1 | 將棧頂引用型數(shù)值存入第二個本地變量 |
0x4d | astore_2 | 將棧頂引用型數(shù)值存入第三個本地變量 |
0x4e | astore_3 | 將棧頂引用型數(shù)值存入第四個本地變量 |
0x4f | iastore | 將棧頂 int 型數(shù)值存入指定數(shù)組的指定索引位置 |
0x50 | lastore | 將棧頂 long 型數(shù)值存入指定數(shù)組的指定索引位置 |
0x51 | fastore | 將棧頂 float 型數(shù)值存入指定數(shù)組的指定索引位置 |
0x52 | dastore | 將棧頂 double 型數(shù)值存入指定數(shù)組的指定索引位置 |
0x53 | aastore | 將棧頂引用型數(shù)值存入指定數(shù)組的指定索引位置 |
0x54 | bastore | 將棧頂 boolean 或 byte 型數(shù)值存入指定數(shù)組的指定索引位置 |
0x55 | castore | 將棧頂 char 型數(shù)值存入指定數(shù)組的指定索引位置 |
0x56 | sastore | 將棧頂 short 型數(shù)值存入指定數(shù)組的指定索引位置 |
0x57 | pop | 將棧頂數(shù)值彈出(數(shù)值不能是 long 或 double 類型) |
0x58 | pop_2 | 將棧頂?shù)囊粋€(對于 long 或 double 類型)或兩個數(shù)值(對于非 long 或 double 的其他類型)彈出 |
0x59 | dup | 復制棧頂數(shù)值并將復制值壓入棧頂 |
0x5a | dup_x1 | 復制棧頂數(shù)值并將兩個復制值壓入棧頂 |
0x5b | dup_x2 | 復制棧頂數(shù)值并將三個(或兩個)復制值壓入棧頂 |
0x5c | dup_2 | 復制棧頂一個(對于 long 或 double 類型)或兩個(非 long 或 double 的其他類型)數(shù)值并將復制值壓入棧頂 ) |
0x5d | dup_2_x1 | dup_x1 指令的雙倍版本 |
0x5e | dup_2_x2 | dup_x2 指令的雙倍版本 |
0x5f | swap | 將棧最頂端的兩個數(shù)值互換(數(shù)值不能是 long 或 double 類型) |
0x60 | iadd | 將棧頂兩 int 型數(shù)值相加并將結果壓入棧頂 |
0x61 | ladd | 將棧頂兩 long 型數(shù)值相加并將結果壓入棧頂 |
0x62 | fadd | 將棧頂兩 float 型數(shù)值相加并將結果壓入棧頂 |
0x63 | dadd | 將棧頂兩 double 型數(shù)值相加并將結果壓入棧頂 |
0x64 | isub | 將棧頂兩 int 型數(shù)值相減并將結果壓入棧頂 |
0x65 | lsub | 將棧頂兩 long 型數(shù)值相減并將結果壓入棧頂 |
0x66 | fsub | 將棧頂兩 float 型數(shù)值相減并將結果壓入棧頂 |
0x67 | dsub | 將棧頂兩 double 型數(shù)值相減并將結果壓入棧頂 |
0x68 | imul | 將棧頂兩 int 型數(shù)值相乘并將結果壓入棧頂 |
0x69 | lmul | 將棧頂兩 long 型數(shù)值相乘并將結果壓入棧頂 |
0x6a | fmul | 將棧頂兩 float 型數(shù)值相乘并將結果壓入棧頂 |
0x6b | dmul | 將棧頂兩 double 型數(shù)值相乘并將結果壓入棧頂 |
0x6c | idiv | 將棧頂兩 int 型數(shù)值相除并將結果壓入棧頂 |
0x6d | ldiv | 將棧頂兩 long 型數(shù)值相除并將結果壓入棧頂 |
0x6e | fdiv | 將棧頂兩 float 型數(shù)值相除并將結果壓入棧頂 |
0x6f | ddiv | 將棧頂兩 double 型數(shù)值相除并將結果壓入棧頂 |
0x70 | irem | 將棧頂兩 int 型數(shù)值作取模運算并將結果壓入棧頂 |
0x71 | lrem | 將棧頂兩 long 型數(shù)值作取模運算并將結果壓入棧頂 |
0x72 | frem | 將棧頂兩 float 型數(shù)值作取模運算并將結果壓入棧頂 |
0x73 | drem | 將棧頂兩 double 型數(shù)值作取模運算并將結果壓入棧頂 |
0x74 | ineg | 將棧頂兩 int 型數(shù)值作負并將結果壓入棧頂 |
0x75 | lneg | 將棧頂兩 long 型數(shù)值作負并將結果壓入棧頂 |
0x76 | fneg | 將棧頂兩 float 型數(shù)值作負并將結果壓入棧頂 |
0x77 | dneg | 將棧頂兩 double 型數(shù)值作負并將結果壓入棧頂 |
0x78 | ishl | 將棧頂兩 int 型數(shù)值左移位指定位數(shù)并將結果壓入棧頂 |
0x79 | lshl | 將棧頂兩 long 型數(shù)值左移位指定位數(shù)并將結果壓入棧頂 |
0x7a | ishr | 將棧頂兩 int 型數(shù)值右(帶符號)移位指定位數(shù)并將結果壓入棧頂 |
0x7b | lshr | 將棧頂兩 long 型數(shù)值右(帶符號)移位指定位數(shù)并將結果壓入棧頂 |
0x7c | iushr | 將棧頂兩 int 型數(shù)值右(無符號)移位指定位數(shù)并將結果壓入棧頂 |
0x7d | lushr | 將棧頂兩 long 型數(shù)值右(無符號)移位指定位數(shù)并將結果壓入棧頂 |
0x7e | iand | 將棧頂兩 int 型數(shù)值作 “按位與” 并將結果壓入棧頂 |
0x7f | land | 將棧頂兩 long 型數(shù)值作 “按位與” 并將結果壓入棧頂 |
0x80 | ior | 將棧頂兩 int 型數(shù)值作 “按位或” 并將結果壓入棧頂 |
0x81 | lor | 將棧頂兩 long 型數(shù)值作 “按位或” 并將結果壓入棧頂 |
0x82 | ixor | 將棧頂兩 int 型數(shù)值作 “按位異或” 并將結果壓入棧頂 |
0x83 | lxor | 將棧頂兩 long 型數(shù)值作 “按位異或” 并將結果壓入棧頂 |
0x84 | iinc | 直接對 int 型變量增加指定值(如i++, i--纺阔, i+=2等) |
0x85 | i2l | 將棧頂 int 型數(shù)值強制轉(zhuǎn)成 long 型數(shù)值并將結果壓入棧頂 |
0x86 | i2f | 將棧頂 int 型數(shù)值強制轉(zhuǎn)成 float 型數(shù)值并將結果壓入棧頂 |
0x87 | i2d | 將棧頂 int 型數(shù)值強制轉(zhuǎn)成 double 型數(shù)值并將結果壓入棧頂 |
0x88 | l2i | 將棧頂 long 型數(shù)值強制轉(zhuǎn)成 int 型數(shù)值并將結果壓入棧頂 |
0x89 | l2f | 將棧頂 long 型數(shù)值強制轉(zhuǎn)成 float 型數(shù)值并將結果壓入棧頂 |
0x8a | l2d | 將棧頂 long 型數(shù)值強制轉(zhuǎn)成 double 型數(shù)值并將結果壓入棧頂 |
0x8b | f2i | 將棧頂 float 型數(shù)值強制轉(zhuǎn)成 int 型數(shù)值并將結果壓入棧頂 |
0x8c | f2l | 將棧頂 float 型數(shù)值強制轉(zhuǎn)成 long 型數(shù)值并將結果壓入棧頂 |
0x8d | f2d | 將棧頂 float 型數(shù)值強制轉(zhuǎn)成 double 型數(shù)值并將結果壓入棧頂 |
0x8e | d2i | 將棧頂 double 型數(shù)值強制轉(zhuǎn)成 int 型數(shù)值并將結果壓入棧頂 |
0x8f | d2l | 將棧頂 double 型數(shù)值強制轉(zhuǎn)成 long 型數(shù)值并將結果壓入棧頂 |
0x90 | d2f | 將棧頂 double 型數(shù)值強制轉(zhuǎn)成 float 型數(shù)值并將結果壓入棧頂 |
0x91 | i2b | 將棧頂 int 型數(shù)值強制轉(zhuǎn)成 byte 型數(shù)值并將結果壓入棧頂 |
0x92 | i2c | 將棧頂 int 型數(shù)值強制轉(zhuǎn)成 char 型數(shù)值并將結果壓入棧頂 |
0x93 | i2s | 將棧頂 int 型數(shù)值強制轉(zhuǎn)成 short 型數(shù)值并將結果壓入棧頂 |
0x94 | lcmp | 比較棧頂兩 long 型數(shù)值的大小瘸彤,并將結果(1, 0 或 -1)壓入棧頂 |
0x95 | fcmpl | 比較棧頂兩 float 型數(shù)值的大小笛钝,并將結果(1质况, 0 或 -1)壓入棧頂; 當其中一個數(shù)值為 “NaN” 時,將 -1 壓入棧頂 |
0x96 | fcmpg | 比較棧頂兩 float 型數(shù)值的大小玻靡,并將結果(1结榄, 0 或 -1)壓入棧頂; 當其中一個數(shù)值為 “NaN” 時,將 1 壓入棧頂 |
0x97 | dcmpl | 比較棧頂兩 double 型數(shù)值的大小囤捻,并將結果(1臼朗, 0 或 -1)壓入棧頂; 當其中一個數(shù)值為 “NaN” 時,將 -1 壓入棧頂 |
0x98 | dcmpg | 比較棧頂兩 double 型數(shù)值的大小蝎土,并將結果(1视哑, 0 或 -1)壓入棧頂; 當其中一個數(shù)值為 “NaN” 時,將 1 壓入棧頂 |
0x99 | ifeg | 當棧頂 int 型數(shù)值等于 0 時跳轉(zhuǎn) |
0x9a | ifne | 當棧頂 int 型數(shù)值不等于 0 時跳轉(zhuǎn) |
0x9b | iflt | 當棧頂 int 型數(shù)值小于 0 時跳轉(zhuǎn) |
0x9c | ifge | 當棧頂 int 型數(shù)值大于或等于 0 時跳轉(zhuǎn) |
0x9d | ifgt | 當棧頂 int 型數(shù)值大于 0 時跳轉(zhuǎn) |
0x9e | ifle | 當棧頂 int 型數(shù)值小于或等于 0 時跳轉(zhuǎn) |
0x9f | if_icmpeq | 比較棧頂兩 int 型數(shù)值的大小瘟则,當結果等于 0 時跳轉(zhuǎn) |
0xa0 | if_icmpne | 比較棧頂兩 int 型數(shù)值的大小黎炉,當結果不等于 0 時跳轉(zhuǎn) |
0xa1 | if_icmplt | 比較棧頂兩 int 型數(shù)值的大小,當結果小于 0 時跳轉(zhuǎn) |
0xa2 | if_icmpge | 比較棧頂兩 int 型數(shù)值的大小醋拧,當結果大于或等于 0 時跳轉(zhuǎn) |
0xa3 | if_icmpgt | 比較棧頂兩 int 型數(shù)值的大小,當結果大于 0 時跳轉(zhuǎn) |
0xa4 | if_icmple | 比較棧頂兩 int 型數(shù)值的大小淀弹,當結果小于或等于 0 時跳轉(zhuǎn) |
0xa5 | if_icmpeq | 比較棧頂兩引用型數(shù)值丹壕,當結果相等時跳轉(zhuǎn) |
0xa6 | if_icmpnc | 比較棧頂兩引用型數(shù)值,當結果不相等時跳轉(zhuǎn) |
0xa7 | goto | 無條件跳轉(zhuǎn) |
0xa8 | jsr | 跳轉(zhuǎn)至指定的 16 位 offset 位置薇溃,并將 jsr 的下一條指令地址壓入棧頂 |
0xa9 | ret | 返回至本地變量指定的 index 的指令位置(一般與 jsr 或 jsr_w 聯(lián)合使用) |
0xaa | tableswitch | 用于 switch 條件跳轉(zhuǎn)菌赖, case 值連續(xù)(可變長度指令) |
0xab | lookupswitch | 用于 switch 條件跳轉(zhuǎn), case 值連不續(xù)(可變長度指令) |
0xac | ireturn | 從當前方法返回 int |
0xad | lreturn | 從當前方法返回 long |
0xae | freturn | 從當前方法返回 float |
0xaf | dreturn | 從當前方法返回 double |
0xb0 | areturn | 從當前方法返回對象引用 |
0xb1 | return | 從當前方法返回 void |
0xb2 | getstatic | 獲取指定類的靜態(tài)域沐序,并將其值壓入棧頂 |
0xb3 | putstatic | 為指定的類的靜態(tài)域賦值 |
0xb4 | getfield | 獲取指定類的實例域琉用,并將其值壓入棧頂 |
0xb5 | putfield | 為指定的類的實例域賦值 |
0xb6 | invokevirtual | 調(diào)用實例方法 |
0xb7 | invokespecial | 調(diào)用超類構造方法堕绩, 實例初始化方法,私有方法 |
0xb8 | invokestatic | 調(diào)用靜態(tài)方法 |
0xb9 | invokeinterface | 調(diào)用接口方法 |
0xba | invokedynamic | 調(diào)用動態(tài)方法 |
0xbb | new | 創(chuàng)建一個對象邑时,并將其引用值壓入棧頂 |
0xbc | newarray | 創(chuàng)建一個指定的原始類型(如 int奴紧, float等)的數(shù)組,并將其引用值壓入棧頂 |
0xbd | anewarray | 創(chuàng)建一個引用型(如 類晶丘,接口黍氮,數(shù)組)的數(shù)組,并將其引用值壓入棧頂 |
0xbe | arraylength | 獲得數(shù)組的長度值并壓入棧頂 |
0xbf | athrow | 將棧頂?shù)漠惓伋?/td> |
0xc0 | checkcast | 檢驗類型轉(zhuǎn)換浅浮, 檢驗未通過將拋出 ClassCastException |
0xc1 | instanceof | 檢驗對象是否時指定類的實例, 如果是, 則將 1 壓入棧頂晓锻,否則將 0 壓入棧頂 |
0xc2 | monitorenter | 獲得對象的鎖奈偏,用于同步方法或同步塊 |
0xc3 | monitorexit | 釋放對象的鎖,用于同步方法或同步塊 |
0xc4 | wide | 擴展本地變量的寬度 |
0xc5 | multianewarray | 創(chuàng)建指定類型和指定維度的多維數(shù)組(執(zhí)行該指令時郁油,操作棧中必須包含各維度的長度值)他炊,并將其引用值壓入棧頂 |
0xc6 | ifnull | 為 null 時跳轉(zhuǎn) |
0xc7 | ifnonnull | 不為 null 時跳轉(zhuǎn) |
0xc8 | goto_w | 無條件跳轉(zhuǎn)(寬索引) |
0xc9 | jsr_w | 跳轉(zhuǎn)至指定的 32 位 offset 位置,并將 jsr_w 的下一條指令地址壓入棧頂 |
上述表格內(nèi)容更為具體的信息可參考 官方JVM指令文檔已艰,除上述表外痊末,還需要認識一些數(shù)據(jù)類型及轉(zhuǎn)化對應規(guī)則,同時再對上述指令的使用場景做一些總結劃分哩掺。
數(shù)據(jù)類型在指令中的轉(zhuǎn)化
數(shù)據(jù)類型 | byte | short | int | long | float | double | char | reference |
---|---|---|---|---|---|---|---|---|
簡化轉(zhuǎn)化 | b | s | i | l | f | d | c | a |
指令集支持的數(shù)據(jù)類型
下面表格中T+指令構成 opcode, T 為上面表格各數(shù)據(jù)類型的簡化轉(zhuǎn)化凿叠。
opcode | byte | short | int | long | float | double | char | reference | |
---|---|---|---|---|---|---|---|---|---|
Tipush | bipush | sipush | |||||||
Tconst | iconst | lconst | fconst | dconst | aconst | ||||
Tload | iload | lload | fload | dload | aload | ||||
Tstore | istore | lstore | fstore | dstore | astore | ||||
Tinc | iinc | ||||||||
Taload | baload | saload | iaload | laload | faload | daload | caload | aaload | |
Tastore | bastore | sastore | iastore | lastore | fastore | dastore | castore | aastore | |
Tadd | iadd | ladd | fadd | dadd | |||||
Tsub | isub | lsub | fsub | dsub | |||||
Tmul | imul | lmul | fmul | dmul | |||||
Tdiv | idiv | ldiv | fdiv | ddiv | |||||
Trem | irem | lrem | frem | drem | |||||
Tneg | ineg | lneg | fneg | dneg | |||||
Tshl | ishl | lshl | |||||||
Tshr | ishr | lshr | |||||||
Tushr | iushr | lushr | |||||||
Tand | iand | land | |||||||
Tor | ior | lor | |||||||
Txor | ixor | lxor | |||||||
i2T | i2b | i2s | i2l | i2f | i2d | ||||
l2T | l2i | l2f | l2d | ||||||
f2T | f2i | f2l | f2d | ||||||
d2T | d2i | d2l | d2f | ||||||
Tcmp | lcmp | ||||||||
Tcml | fcml | dcml | |||||||
Tcmpg | fcmpg | dcmpg | |||||||
if_TcmpOP | if_icmpOP | if_acopOP | |||||||
Treturn | ireturn | lreturn | freturn | dreturn | areturn |
大部分指令沒有支持 byte,char 和 short 甚至是 boolean嚼吞,編譯器會在編譯期或運行期把這類數(shù)據(jù)擴展為 int 類型數(shù)據(jù)盒件。
加載/存儲指令
加載/存儲指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來回傳輸。
- 將一個局部變量加載到操作棧:Tload舱禽,Tload_n炒刁。后者表示是一組指令。
- 將一個數(shù)值從操作數(shù)棧存儲到局部變量表:Tstore 誊稚,Tstore_n翔始。后者表示是一組指令。
- 將一個常量加載到操作數(shù)棧:Tipush里伯,ldc城瞎,T_const 等
- 擴充局部變量表的訪問索引指令:wide
運算指令
對操作數(shù)棧的數(shù)值進行運算之后把結果重新存入操作棧棧頂。
- 加法指令Tadd
- 減法指令Tsub
- 乘法指令Tmul
- 除法指令Tdiv
- 求余指令Trem
- 取反指令Tneg
- 位移指令Tshl疾瓮,Tshr脖镀,Tushr
- 按位或指令Tor
- 按位與指令Tand
- 按位異或指令Txor
- 局部變量自增指令Tinc
- 比較指令Tcmpg ,Tcmpl
類型轉(zhuǎn)化指令
類型轉(zhuǎn)化指令用于將兩種不同的數(shù)值類型進行相互轉(zhuǎn)換狼电,這種轉(zhuǎn)換操作一般用于實現(xiàn)用戶代碼中的顯式轉(zhuǎn)換操作蜒灰,或者用于處理字節(jié)碼指令集中數(shù)據(jù)類型相關指令無法與數(shù)據(jù)類型一一對應的問題弦蹂。
- int類型轉(zhuǎn)其他i2T
- long類型轉(zhuǎn)其他l2T
- float類型轉(zhuǎn)其他f2T
- double類型轉(zhuǎn)其他d2T
對象創(chuàng)建與訪問指令
盡管類實例和數(shù)組都是對象,但 Java虛擬機 對類實例和數(shù)組的創(chuàng)建與操作使用了不同的字節(jié)碼指令强窖。
- 創(chuàng)建類實例new
- 創(chuàng)建數(shù)組newarray凸椿,anewarray,multianewarray
- 訪問類變量和實例變量getfield毕骡,putfield削饵,getstatic,putstatic
- 把一個數(shù)組元素加載到操作數(shù)棧Taload
- 將一個操作數(shù)棧的值存儲到數(shù)組元素中Tastore
- 取數(shù)組長度的指令arraylength
- 檢查類實例類型instanceof, checkcast
操作數(shù)棧管理指令
- 將操作數(shù)棧棧頂一個或者兩個元素出棧pop未巫,pop2
- 復制棧頂一個或兩個數(shù)值并將復制值重新壓入棧頂dup窿撬,dup2,dup_x1叙凡,dup2_x1劈伴,dup_x2,dup2_x2
- 將棧最頂端兩個數(shù)值互換swap
控制轉(zhuǎn)移指令
讓虛擬機可以有條件或者無條件地從特定位置指令執(zhí)行程序而不是在控制轉(zhuǎn)移指令的下一條指令執(zhí)行程序握爷。
- 條件分支ifeq跛璧,ifit,ifle新啼,ifne追城,ifgt,ifge燥撞,ifull座柱,ifnonnull,if_icmpeq物舒,if_icmpne色洞,if_icmplt,if_icmpgt冠胯,if_icmple火诸,if_icmpge,if_acmpeq荠察,if_acmpne
- 復合條件分支tableswitch置蜀,lookupswitch`
- 無條件分支goto,goto_w割粮,jsr盾碗,jsr_w,ret
方法調(diào)用和返回指令
- 調(diào)用對象的實例方法invokevirtual舀瓢,根據(jù)對象的實際類型進行分派
- 調(diào)用接口方法invokeinterface, 會在運行時搜索一個實現(xiàn)了這個接口的方法的對象,找到適合的方法進行調(diào)用
- 調(diào)用一些需要特殊處理的實例方法invokespecial耗美,包括實例初始化方法京髓,私有方法和父類方法
- 調(diào)用類方法invokestatic 用于調(diào)用 static 方法
- 運行時動態(tài)解析處調(diào)用點限定符所引用的方法并執(zhí)行該方法invokedynamic航缀,區(qū)別于前面4條指令,它們都在固化在 JVM 內(nèi)部堰怨,而該指令的分派邏輯是由用戶所設定的引導方法決定的芥玉。
異常處理指令
athrow指令用于完成顯式拋出異常(throw語句)的操作,除了用throw語句之外备图,JVM 還規(guī)定在運行時會在其他 JVM 指令檢測到異常狀況的時候自動拋出灿巧。比如當除數(shù)為 0 時 JVM 會在idiv或ldiv中拋出ArithmeticException異常。
同步指令
JVM的同步有一下場景揽涮,都是使用管程(Monitor)來支持
- 方法級的同步抠藕,不需要字節(jié)碼控制,實現(xiàn)于方法調(diào)用和返回操作志宏蒋困。從方法表中ACC_SYNCHRONIZED得到一個方法是否是同步盾似,如果被設置,則執(zhí)行線程需要先持有管程才能執(zhí)行雪标,執(zhí)行完之后釋放管程零院。
- 方法內(nèi)部一段指令序列的同步,由指令 monitorenter 和 monitorexit 來支持 synchronized 完成村刨。
我們的 ?? 非常簡單告抄,實際上用不到這么多指令的,其他的可備份用于查詢嵌牺。
指令集輔助解析 Code
有了上述指令集幫助及膝打洼,回到天書中的第 29-36 行內(nèi)容重新解析。
第29-32行為 第1個方法
, 查看方法表可知 訪問標志(0001)為 public, 名字(0007)為 < init >, 描述(0008)為 ()V, 一個屬性(0001)髓梅。由于存在一個屬性拟蜻,繼續(xù)查看屬性。從 30 行開始解析屬性 0009 解析為 第9項常量
Code, 查看屬性表-Code屬性結構表及結合指令集中操作符信息枯饿, Code 屬性最終的內(nèi)容如下酝锅。(看到這里,你應該嘗試過一遍哦 ??)
第33-36行為 第2個方法
, 查看方法表可知 訪問標志(0001)為 public, 名字(000B)為 inc, 描述(000C)為 ()I, 一個屬性(0001)奢方。也存在一個屬性搔扁。從 34 行開始解析屬性 0009 解析為 第9項常量
Code, 內(nèi)容如下。
至此蟋字,class文件內(nèi)容基本確定可知稿蹲。
為了驗證我們的思路是否正確,可以通過javap查看 TestClass.class 的結構來進行對比鹊奖。
除了javap幫我們做了格式化的工作外苛聘,也是按照我們分析字節(jié)碼的邏輯來進行內(nèi)容的輸出, 感興趣的伙伴可以查看javap內(nèi)部實現(xiàn)。
當你讀懂Class文件之后,你就可以進一步做很多工作了设哗,比如借助 ASM 框架入侵 Gradle 構建流程注入靜態(tài)代碼等唱捣,更多場景等你挖掘。
如果文章對您有幫助网梢,歡迎點贊評論支持~