如何讀懂晦澀的 Class 文件

Android開發(fā)者功能開發(fā)幾乎都是面向Java/Kotlin語法編程咱揍,對于class文件的關注相對較少刀崖。 當你反編譯class文件或程序編譯期修改字節(jié)碼亦做代碼注入時惊科,讀懂字節(jié)碼成為一道繞不開的檻。

文章主要描述如何快速讀懂一個class文件亮钦。涉及到的 JVM 指令及字節(jié)碼結構已做了整理馆截,這部分知識平時用到的時候查一下便可,用多了自然記住了蜂莉。即使你是一個新手蜡娶,按照下面的思路整合,你也可以從 0 上手映穗。

讀完本篇文章你會收獲:

  1. Class 文件結構長啥樣
  2. JVM 操作指令有哪些
  3. 如何從二進制流中讀懂 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ù)特定表格進行查詢解析躏惋,具體如下:

  1. 常量池對應常量表約束
  2. 訪問標志對應訪問標志表約束
  3. 字段對應字段表約束
  4. 方法對應方法表約束
  5. 屬性對應屬性表約束,同時屬性內(nèi)可能還需要進一步劃分嚷辅,對應Code屬性結構, 異常屬性結構等表約束
  6. 還有一些特殊字符串格式約束簿姨,比如特殊字符串表等等

常量表

常量池主要存放兩種類型

  • 字面量,包含文本字符串簸搞,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_pcline_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 關鍵字修飾但沒有被聲明為 finalabstract

第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

大部分指令沒有支持 bytecharshort 甚至是 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
  • 比較指令TcmpgTcmpl

類型轉(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凸椿,anewarraymultianewarray
  • 訪問類變量和實例變量getfield毕骡,putfield削饵,getstaticputstatic
  • 把一個數(shù)組元素加載到操作數(shù)棧Taload
  • 將一個操作數(shù)棧的值存儲到數(shù)組元素中Tastore
  • 取數(shù)組長度的指令arraylength
  • 檢查類實例類型instanceof, checkcast

操作數(shù)棧管理指令

  • 將操作數(shù)棧棧頂一個或者兩個元素出棧pop未巫,pop2
  • 復制棧頂一個或兩個數(shù)值并將復制值重新壓入棧頂dup窿撬,dup2dup_x1叙凡,dup2_x1劈伴,dup_x2dup2_x2
  • 將棧最頂端兩個數(shù)值互換swap

控制轉(zhuǎn)移指令

讓虛擬機可以有條件或者無條件地從特定位置指令執(zhí)行程序而不是在控制轉(zhuǎn)移指令的下一條指令執(zhí)行程序握爷。

  • 條件分支ifeq跛璧,ifitifle新啼,ifne追城,ifgtifge燥撞,ifull座柱,ifnonnullif_icmpeq物舒,if_icmpne色洞,if_icmpltif_icmpgt冠胯,if_icmple火诸,if_icmpgeif_acmpeq荠察,if_acmpne
  • 復合條件分支tableswitch置蜀,lookupswitch`
  • 無條件分支gotogoto_w割粮,jsr盾碗,jsr_wret

方法調(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 會在idivldiv中拋出ArithmeticException異常

同步指令

JVM的同步有一下場景揽涮,都是使用管程(Monitor)來支持

  • 方法級的同步抠藕,不需要字節(jié)碼控制,實現(xiàn)于方法調(diào)用和返回操作志宏蒋困。從方法表中ACC_SYNCHRONIZED得到一個方法是否是同步盾似,如果被設置,則執(zhí)行線程需要先持有管程才能執(zhí)行雪标,執(zhí)行完之后釋放管程零院。
  • 方法內(nèi)部一段指令序列的同步,由指令 monitorentermonitorexit 來支持 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)代碼等唱捣,更多場景等你挖掘。

如果文章對您有幫助网梢,歡迎點贊評論支持~

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末震缭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子战虏,更是在濱河造成了極大的恐慌拣宰,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烦感,死亡現(xiàn)場離奇詭異巡社,居然都是意外死亡,警方通過查閱死者的電腦和手機啸盏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門重贺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人回懦,你說我怎么就攤上這事气笙。” “怎么了怯晕?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵潜圃,是天一觀的道長。 經(jīng)常有香客問我舟茶,道長谭期,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任吧凉,我火速辦了婚禮隧出,結果婚禮上,老公的妹妹穿的比我還像新娘阀捅。我一直安慰自己胀瞪,他們只是感情好,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布饲鄙。 她就那樣靜靜地躺著凄诞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忍级。 梳的紋絲不亂的頭發(fā)上帆谍,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音轴咱,去河邊找鬼汛蝙。 笑死烈涮,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的患雇。 我是一名探鬼主播跃脊,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宇挫,長吁一口氣:“原來是場噩夢啊……” “哼苛吱!你這毒婦竟也來了?” 一聲冷哼從身側響起器瘪,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤翠储,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后橡疼,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體援所,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年欣除,在試婚紗的時候發(fā)現(xiàn)自己被綠了住拭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡历帚,死狀恐怖滔岳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挽牢,我是刑警寧澤谱煤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站禽拔,受9級特大地震影響刘离,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜睹栖,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一硫惕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧野来,春花似錦恼除、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搪锣,卻和暖如春秋忙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背构舟。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工灰追, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓弹澎,卻偏偏與公主長得像朴下,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子苦蒿,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容