如何读懂晦涩的 Class 文件|进阶必备


Android 开发者日功用常开发几乎都是面向 Java/Kotlin 语法编程,关于.class 文件的重视相对较少。 当你反编译 .class 文件或在 Android 程序编译期间修改字节码做代码注入时,读懂字节码是一道绕不开的槛。

文章主要给出快速读懂一个 class 文件的办法,触及到W * % % W ; a W的 JVM 指令及字节码结构已做了收u # } X –拾,这部分常识平常用到的时分查一下便C z ? +可,用多了天然记p K % F M . PN L ( . y = Y ) Z了。 即便你是一个新手,依照下面的思路整合,x F o | q ] | z y你也能够从 0 上手。

读完本篇文章你会收成:! O 4 e c m

  1. class 文件结构长啥样
  2. JVM 操作指令有哪些
  3. 怎样从二进制流中读懂 class 文件

举个栗子 带j @ i `你入门

编写一个简略的 .java 文件

如何读懂晦涩的 Class 文件|进阶必备

运用 javac 编译 TestClass.java 输出 TestClass.ClaU a P 5 . sss,得到的二进制流文件。能够通过东西检查其内容,在 MAC 平台上引荐运用 iHex-Hexu D R u Editor 以十六进制格局检查,大概长这个样子i D / B ^ Z i 5 I

如何读懂晦涩的 Class 文件|进阶必备

这看起来像 “天书”,无从下手。实际上,任何编程产物终究都会演化成二进制,它必定N a h J ` =是依照某种规矩来, n – @ i 8 j :申明某种逻辑的x } H z。 Java 虚拟机为了能够解析这个文件,要求其内容有必要依照严厉格局来排版,这种结构格局便是 Class 文件结构。可是单单直到里边有哪些内容还不行,虚拟机还需求一套规矩来操作这些内容,这些规矩便是 字节码操作指令

要读懂这些 “天书”,先得了解 “天书” 是怎样写出来的。

class 文件结构长啥样

.java 文件通过 JAVA编译器(javac)编译成中间代码w _ k U ? ! 1 `字节码 .class 文件.class 文件 是一个二进制文件,里边的内容现已是严厉依照 Class 文件结构规矩排列的。

下面表明是 Class 的文件结构表,依次依照行从上到下解析,也便是说文件最初优先解析 magic(魔数)。

类型 称号 描绘 数量
u4(4个字节) magic 承认该文件是否为一个能被虚拟机承受的Class文件,相似于ID 1
u2(2# , $个字节) minot_version 次版别号 1
u2(C C y m t z j 4 ^2个字节) mahor_version 主版别号 1
u2(2个字节) c@ ? m q s & Fonstant_pool_count 常量池容量计数值,从1开端核算,0则表明不引证任何一个常量池项目 1
c% j ! 9 ( 9 (p_info constant_pool 常量池 constant_pB / Fool_cof q 9 N 7unt-1
u2(2个字节) access_flags 拜访标志 1
u2(2个字节) this_class 类索引 1
u2(2个字节) super_class 父类索引 1Q W a a
u2(2个字节) interfaces_count 完成接口的数目 1
u2(4个字节) interfaces 接口索引 interface; W , Q r O :s_count
u2(4个字节) fields_count 字段的数目 1
field_info fields 字段内] 6 R fields_count
u2(2个字节) methods_count 办法的数目 1
met7 C T P – b m 9hod_info methods 办法内容 methods_count
u2(= g m2个字节) attributes_count 特点的数目 1
attribu; ; ] ^ * Y 8 kte_info attributes 特点内容 attributes_count

单靠上面的表还不行,其间描绘列中部分内容在字节码层面的描绘,还需求依据特定表格进行查询解析,详细如下:

  1. 常量池对应 % { P量表 束缚
  2. 拜访标志对应 拜访标志表 束缚
  3. 字段对应 字段表 束缚
  4. 办法对应 办法表 束缚
  5. 特点对应 特点表 束缚,一起特点内或许还需求进一步区分,对应 Code特点结构, 反常特点结构 等表束缚
  6. 还有一些特别字符串格局束缚,比方 特别字符串表 等等

常量表

常量池主要存放两种类型

  • 字面量,包含文本字符串,] R G s mfinal的常量值等
  • 符号引证,类和接口的全限定名,字段的称号和描绘符,办法的称号和描绘符

Class文件只保存各3 L 1 K 4个办法,字段信息,不保存内存信息。只要通过运行期转化才干得到真正的内存进口。当虚拟机运行时,需求从常量池中获取到对应的符号引证,再通过类创立者运行时解析,得到详细的内存地址。

类型 子结构 标志 B w G
CONSTANT_Ul w ^ d @ a _ O Ptfx 4 ` ,8_info tag u1 = 1 UTF-8编码的字符串
lenght u2 UTF-8编码的字符串占用的字节数
bytes u1 长度为lenght的UTF-8编码的字符串
CONSTANT_Integer& Y q D i A_info tag u1=3 整型字面量
bytes u4 依照高位在前存储的int值
CONSTANT_Float_info tG K }ag u1=4 浮点型字面量
bytes u4 依照高位在前存储的float值
CONSTANT_Long_info tag u1=5r ! q C q 长整型字* 3 r 0 T ` # x面量
bytes u8 依照高位在前存储的long值
CONSy Z ) ( |TANT_Double_info tag u1=6 双精度浮点型字面量
bytes u8 依照高位在前存储的double值
CONSTANT_Class_L ] 0 } hinfo tag u1=7 类或E [ B i #接口的符号引证
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 的索Y C { K B j引项
CONSTANT_Methodred_info tag u1=10 类中办法的符号引证
index u2 指向声明字段的类或许接口描绘符 CONSTANT_Class_info 的索引项
index u2 指向声明字段的类或许接口描绘符CONSTANT_No ! C Ead j TmeAndType_info 的索引项
CONSTANT_I~ i G tnterfaceMethodref_info tx K y I }ag 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 表明办法句柄
refB _ )erence_kind u1 值有必要在[1,9]中,它决议了办法句柄的类型。办法句柄类型的值表明办法句柄的字节码行为
reference_index u2Y U T – 值有必要是对常量池的有用索引
CONSTANT_MethodType_info tag u1=16 辨认办法类型
descriptor_index u2 值有必要是对常量池的有用索引,常量池1 $ ` { o 8在该索引处的项有必要是CONSTAu 2 r a [ b O 5NT_Utf8_info结构,表明办法的描绘符
CONSTANT_InvokeDynam– 7 b ] p # / F ^ic_info tag u1=18 表明一个动态办法调用点w v q
bo@ ] rotstrap_method_attar_index u2 值有必要是对当时Class文件中引导办法表的 bootstrap_methods[]数组的有用索引
name_and_type_index u2 值有必要是对当时常量池的有用索引,常量池在该索引处的值有必要是CONSTANT_NameAndTypeI = E_info结构,表明办法名和办法描m m Z X q ^绘符

拜访标志表

针对类,字段表,办法表中的拜访标志进行区分。

  • 类拜访标志,用于辨认一些类或许接口层次的拜访信息, 包含这个Cw ` O , ilass是类仍是接口,是否被界说成public类, / D & ; 7 o 1 1型,是否被界说成abstract类类型,假如P p – s o ;是类的话,是否被声明b L A { p X K为final等等

    标志称号 标志值 描绘
    ACC_PUBLIC 0x0001 是否为public类型
    ACC_FINAL 0x0010 是否被声明为final,只要类可设置
    ACC_SUPER 0x0020 是否允许运用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改& 0 i 8变,为了区别这条指令运用哪种语意,JDK1.0.2之后编译出来的类的这个= ~ z o E _ j I q标识有G * ~ C ! ( – / R必要都为真
    ACC_INTERFACE 0x0200 标识这个是一个接口
    ACC_ABSTRACT 0x0400 是否为abstract类型,关于接口或许抽象类来说,Q ? w ]此标志的值都为真,其他类型为假
    ACC_SYNh ~ a GTHETIC 0x1000 标识这个类并非由用户代码发生的
    ACC_ANNOTATION 0x2000 标识这是一个注解+ v – u
    ACC_ENUM 0x4000 标识这是一个枚举
  • 内部类拜访标志

    标志称号 标志值 描绘
    ACC_PUBLIC 0x0001 内部类是否为public
    ACC_PRIVATE 0x0002 内部类是否为private
    ACA # W w 3 aC_PROTECTED 0x0004 内部类是否为protected
    ACC_STATIC 0x0008 内部类是否为protei i I 1 w i V tcted
    ACC_FINAL 0x0010 内部类是否为protected
    ACC_INTERFACE 0x002U $ $0 内部类是否为接口
    ACC_ABSTRACT 0x0400 内部类是否为abstract
    ACC_SYNTHn M FETIC 0x1000 内部类是否并非由用户代码c E O发生
    ACC_ANNOTATION 0x2000 内部类是否是一个注解
    ACC_ENUM 0x4000 内部类是否是一个枚举
  • 字段拜访标志

    标志称号 标志值 描绘
    ACC_PUBLIC 0x0001 字段是否为public
    ACC_PRIVA/ v i )TE 0x0002 字段是否为private
    ACC_PROTECTED 0x0004 字段是否为protected
    ACC_STATIC 0x0008 字段是否为stat9 h ` ! I O lic
    ACC_FINAL 0x0010 字段是否为final
    ACC_VOLATILE 0x0040 字段是否为e e F %volatile
    Au Z HCC_TRANSIENT 0x0080 字段是否为transient
    ACC_SYNm & 0 $ #THETIC 0x1000 字段是否由编译器主动发生的
    ACC_ENUM 0x4000 字段是否为enum
  • 办法拜访标志

    标志称号 标志值 描绘
    ACC_PUBLIC 0x0001 办法是否为public
    ACC_PRIVATE 0x0002 办法是否为private
    ACC_PROTECTED 0x0004 办法是否为protecU F K M P / 9ted
    ACC_STATIC 0x0008 办法是否为static
    ACC_FINAL 0x0010 办法是否为final
    ACC_SYNCHRONIZED 0x0020 办法是否为synchronized
    ACC_BRIDGE 0x0040 办法是否由编译器发生的桥接办H . a Y C a
    ACC_VARARGS 0x0080 办法是否承受不定参数
    ACC_NATIVE 0x0100 办法是否为native
    ACC_ABSTRACT 0x0400 办法是否为abstract
    ACC_STRICTFP 0x08l ( e h |00 办法是否为strictfp
    ACC_SYNTHETIC 0x1000 C . z 9 A $法是否由编译器主动发生的

字段表

用于描绘接口和类中声明的变量,^ & y 8包含类等级变量以及实例等级变量

类型 称号 数量
u2 access_flags 1
u2 nC = s name_index 1
u2 descriptor_index 1
u2 attribu~ E ! d e ] Rtes_cy , D d | s q ~ yount 1
u2 attributes attributes_Q r E c h 8 o lcount

其间 ac ~ lccess_flags 见上面拜访标u W 3 r 5志表中的字段拜访标志

办法表

办法表包含拜访标志,称号索引和描绘符索引,特点信息等几项

? O 称号 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info att 2 =ributes attributes_countn F + , G

其间办法的access_flags见上述的办法拜访标志

特点表

特点表用于f ~ u ` j G P s X解说Class文件中字段表,办法表中带着的特点表调集,用于描绘某些场景专有的信息。

特点称号 H s A a V V u 3 m用方位 意义
Code 办法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字界说的常量值
Deprecated 类,办法表,字段表 final关键字界说的常量值
Exceptions 办法表 final办法抛出的反常
EnclosingMethod 类文件 仅当一/ p P ; 3 [ #个类为局部类或许匿名类时才干拥T S –有这个特点,这个特点用于标识这个类地点的外围办法
InnerClasseb G ~ . ,s 类文件 内部类列表
LineNumberTable Code特点 Java源码的行号与字节码指令的对应联系
LocalVariableTable Code特点 办法的局部变量描绘
StackMapTable Code特点 JDK1.6中新增的特点,供新的类型检查校验器(Type Checker)检查和处理目标办法的局部变量和操作数栈锁需求的类型是否匹配
Signature 类,办法表,字段表 JDK1.5中新增的特点,这个特点用于支撑泛型情况下的办法签名,在java言语中,任9 k 9 , . r 1何类,接口,初始化办法或成员的泛型签名假如包含了类型变量(Type Variables)或许参数化类型(Parameterized Types),则Signature特点会为它记载泛型a ( 0签名信息。因为jai s J D 1 Eva的泛型选用擦除法完成,在为了类型信息被擦除后导致签名紊乱,需求) A t这个特点记载H q D j s G泛型中的相关信息
SourceFile 类文件 记载源文件称1 h z y ! , Z $ U
SourceDebugExtension 类文件 JDK1.6中新增的特点,SourceDebugExtension特点用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java仓库[ l p i ! A J来定位到JSP文件的行号,JSR-4Z ! Z g G m5标准为这些V H . K # . h $ /非Java言语编写,却需求编译成字节码并+ c & 9 P r运行在Java虚拟机中的程序供给了一个] ! O K H k进行调试的标准机制,运用SourceDebugExtension特点就能够用于存储这个2 ( } w u标准所新加入的调试信息
SyntheC ^ / Z * Qtic 类,办法表,字段表 标识办法或许字段是否为编译器主动生成的
LocalVariableTypeTable JDK1.5中新增的特点,它运用特征签名替代描绘符,是为了引入泛型语法之后能描绘泛+ y : 0 l型参数化类型而增加的
Ru# . e ^ ! @ ` p MntimevisibleAnnotar O , N C ; ;tions 类,办法表,字段表 JDK1.5中新增的特V 6 * Z q a %点,为动态注解供给支撑。RuntimevisibleAnnotation[ F v d rs 特点用于指明哪% f 2 Y & G ) a v些注解是运行V q f ( s P & k : ] a t c(实际上运行时便是进行反射调用)可见的
RuntimeInvisibleAnnotations 类,办法表,字段表 JDK1.5中新增的特点,与 RuntimevisibleAnnotations 特点效果刚好相反, 用于指明哪些注解是运行时不行见的
RuntimeVisibleParameterAnnotations 办法表 JDK1.5中新增的特点,效果与 RunF K V _timevisibleAnnotations 特点相似,只不过效果目标7 U N ( }为办法参数e # c
RuntimeInvisibleParap H _ Q ? c S _ GmeterAnnotations 办法表 JDK1.5中新增的特点,效果与 RuntimeInvisT B p %ibleAnnotations 特点3 M Z $ ; –相似,只不过效果目标为办法参数
Annota0 z & ; ctionDetault 办法表4 ` i Z JDK1.5中新增的特点,用于记载注解类I o l e y o 5 ` v元素的默许值
BootstrapMethods 类文件 JDK1.5中新增的特点,用于保存 invokedynamic 指令引证的引导办法限4 X @定符

上述的每一个特点,都需求从常量池中引证一个 CONSTANT_Utf8_info类型常量来标S G )明。还包含attribute_length(u4)用于标明特点值所占用的位数,后边再跟着特点内容。下面为一些常见的特点子表结构。

  • 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_lenght
    u2 exception_table_lenght 1
    ex| b U k r Z m ! sception_info exception_table exception_table_length
    u2 attributes_count 1
    attribute_info atu k w ptributes attributes_count
  • 反常特点结构表,用于描绘反常信息

    类型 称号 数量
    u2F ! L X start_pc 1
    u2 end_pc 1
    u2 handler_pc 1
    u2 catch_type 1
  • Exceptions特点结构表

    区别与反常表,该表主要是罗列中办法中或许抛出的受检查反常,也便是办法描绘时throws关键字罗列的反常

    类型 称号 数量
    u2 attribute_name_index 1
    u4 att! 7 I F Uribute_length 1
    u2 number_of_exceptions 1
    u2 exception_index_table nu_ 1 A & D 6 k i nmber_of_exceptions
  • LineNumberTable] T V ix p n p . E =点结构表

    用于描绘Java源码行号与字节码行号之间的对应联系,默许声称到Class文件中。

    类型 称号 数量
    u2 attribute_name_index 1
    u4 attribute_length 1
    u2 line_number_table_length 1
    line_number_info line_number_taby a Yle line_number_table_length

    其间l6 = V * U Fine_number_info包含start_pc和line_number两个u2类型的数据项。

  • Lq a A { l 0ocalVariay x I = + s ? | VbleTable特点? ^ . – }结构X 1 v e 4 * V x6 N ,

    用于描绘栈帧中局部变量表中的变量与Java源码中界说的变量之间的联系,默许生成到Class! D # 7 f文件中

    类型 称号 数量
    u2 attribute_name_index 1
    u4 attribute_length 1
    u2 local_variable_table_l. 8 Q s n ] denght 1
    localo l i_variable_info local_variable_table locx p 8 t B | ^ Pal_variaw & J & / 3 $ 0 Sble_table__ 7 3lenght

    其间 local_variab3 7 P i G Ble_info是代表栈帧与源码中局部变量的相关,见下表

    类型 称号 意义 数量
    u2 start_pc 局部变量的生命周期开端的字节码偏移量 1
    u2 length= 9 A a _ 局部变量的生命周期开端的效果范围覆盖长度 1_ n O { c ( O h
    u2 name_index 指向常量池 CONSTANT_Utf8_info 索引 1
    u2! X 8 * R – descriptor_indx o 8 5 ^ :ex 指向常量池 CONSTANT_Utf8_info 索引 1
    u2 index 局部变量在栈帧局部变量表中Slot的方位 1
  • SourceFile特点结构表

    用于记载生成这个Class文件的4 . p 3 ? t源码文件称号

    类型 称号 数量
    u2 attribute_name_index 1
    u4 attribute_length 1
    u2 sourcefile_index 1

    其间 sourcefile_index为指向常量池 CONSTANT_Utf8_info 索引

  • ConstantValue特点结构表

    用于告诉虚拟机主动为静态变量赋值。只要被static关键字润饰的变量才干够运6 a f W h ) T用这项特点。

    类型 称号 数量
    u2 attribute_name_index 1
    u4 attribute_length 1
    u2 constant_index 1
  • InnerClasses特点结构表

    用于记载内部类与宿主类之间的相关,假如一个类中界说了内部类,编译[ – W I ? K器则会为它生成内部类IN_ f 1nerClasses特点

    类型 称号 数量
    u2 attribute_name_index 1
    u4 attribute_length 1
    u2 nb % ] , { w , # ;umber_of_classes 1
    inner_classes_X _ r T s ) –info inner_classes number_of_classes

    每一个inner_cla( 8 (sses_info代表j A G H g v一个内部类信息O ; D l L i,结构如下

    类型 称号 意义 数量
    u2 inner_class_info_index 指向常量池 CONSTANT_Class_info 索引 1
    u2 outer_class_info_index 指向常量池 CONSTANT_Class_info 索引 1
    u2 i9 X K dnner_name_index 指向常量池l n M s 3 / N CONSTANT_Utf8_info 索引,代表这个内部类的称号,假如匿名则为0 1
    u2 inner_class_access_f% z g K h { q Xlags 内部类的拜S L P j X J a访标志,见上述拜访标# O ( R志篇章 1
  • Deprecated/Synthetic特点结构表

    前者是用于标明某个类,字段或许办法是否不再引荐运用

    后者是用于标明字段或许办法不是由Java源码直接发生,一切由非用户代码生成的办法都需求设置Synthc V Metic特点或许ACC_SYNTHETIC标志,可是和在外。他们的结构如下

    类型 称号 数量
    u2 attribute_name_index 1
    u4 attribute_length 1% ; E 9 m b 4 !
  • StackMapTaF q r R g h Nble特点结1 @ U构表

    于JDK1.6之后增加在Class标准中,位于Code特点表中,该特点会在虚拟机类加载的字节码校验阶段被新类型检查查验器(Type Checker)运用。

    类型 称号 数量
    u2 attribute_name_index 1
    u4. f B attribute_leng R ] i | A J Fgth 1
    u2 number_of_entries 1
    stack_map_frame stack_map_frame_entries number_of_x A I 6 zentries
  • Signature特点结构表

    于JDK1.5发布之后增加到Class标准中,它是一个可选的定长特点,能够出现在类,特点表,办法表结构的特点表中。该特点会记载泛型签名信息,在Java言语中泛型c I _ p Z选用的是擦除法完成的伪泛型,在b P z @ 3 J字节码(Code特点)中8 x D – a g,泛型信息编译之后都统统被擦除掉。因为无法像C#等运行时支撑获取真泛型类型,G W R Q c s a增加该特点用于弥补该缺陷,现在Java反射现已能获取到泛型类型。

    类型 称号 数量
    u2 attribute_{ 4 Nname_index 1
    u4 attriX ] `bute_length 1
    u2 signat^ b 9 C N M ` J ^ure_index 1

    其间 signature_index 值有必要是一个对常量池的有用索引且为 CONSTANT_Utf8_info,表明类签名,办法类型签名或字段类型签名。假D O p G 2 G ^ Z )如当时Signature特点是类文件的特点,则这个结构表明类签名,U k z J假如当时Signature特点是办法表的特点,则表明办法类型签名,假如当时Signature特点是字段表的特点,则表明字段类型签名。

  • BootstrapMethods特点结构表

    于JDK1.l 5 g V & 5 x @ *7发布后增加i q , D = k Q q到Class文件标准中,是一个杂乱变长的特点,位于类文件的特点表中。

    类型 称号 数量
    u2 attribute_name_index 1
    u4 attribute_lengC k } B G j + 1 Vth 1
    u2 num_bootstrap_method| v ps 1
    bootstrap_method bootstrap_met9 b n u A { P 3hods num_bootstrapU 3 { * ~ 0_methods

    其间bootstrap_method结构如下

    类型 称号 数量
    u2 bootstrap_method_ref 1
    u2 num_bootstra8 h v & 1p_arguments 1
    u2 bootstrap_arguments numv r _ 5 t S_bootstrap_c B g ` L targuments

特别字符串

  • 全限定名,比j % Z G U方 com/yummylau/TestClass 其实便是把类全名的 “.” 换成 “/”,能够运用多个 “;”分割多个全限定名

  • 简略称c * y f 8 d c a u号,没有类型和| / p参数润饰的M _ & & } a M I办法或许字段的姓名,q 5 T x 1 D o比方办& { c . O 9 }法 inc() 和字段 m 分别标明为 inc 和 m

  • 描绘符

    标识字符 意义
    B 根本类型 byte
    C 根本类型 char
    D 根本类! G g F型 double
    F 根本类型 float
    I 根本类型 int
    J 根本类型 long
    S 根本类型 short
    Z 根本类型 boolean
    V 根本类型 void_ * : 7 e
    L 目标类型,比方 Ljava/law I E 4 | L z 3ng/Object
  • 针对数组,每一个维度运用一个前置的”[“字v + } =符来描绘,比方界说一个 “java.lang.String[][]”数组,被H a B z [ Q s记载为“[[java.lang.String;”一个整型数组 “in z 9t[]G S I” 被记载为[I

  • 针对办法,运用以下描绘符

    办法场景 描绘符
    void inc() ()V
    java.lang.String toString()& ? U F M O t 5 ()Ljava/lang/String;
    int iF Q 4 a A ` %ndexOf(char[w R Z D N % ]source,int sourceOffest,int sourceCount,char[] target,int targetOffset,int targetCOunt,int formIndex) ([CII[CIII)I

表格比较多,可是一般在运用的过程中Q . g @ = : N w ?逐渐查找就能够了。

从头认识字节天书

如何读懂晦涩的 Class 文件|进阶必备

这份字节天书,我从头依照上述的 class文件结构表 的字节区间规矩,从头对字节进行了排版。

如何读懂晦涩的 Class 文件|进阶必备

行从上到下一一对应 class文件结构表中的行。下面开端解析每行字节的意义,根本的逻辑都是索引到某个数据结构,该数据结构对应上述的某一张表格。

第1行魔数, 主N s b 4 e m t要是用于承认这个文件是否能被虚拟机加载, CAFEBABE 其实便是 Baristas咖啡

第2行主次版别号 , 从 java-class版别对应 可知表明 Java SE 10, 向下兼容到 JDK1.1。

第3行常量池中常量数量 , 因为从 1 开端计数,第 0 项预留用于表明“不引证任何一个常量池项目”, 转化 16 进制之后可知常量池有 18 项常量。 每一项常量都对应 常量表 的某一项, 依照表中规定的每一项常量对G ! P q应各自的结构。

第4行第 1 项常量,为类中办法的符号引证, 格局为 {第4项常量}.{第15项常量} ,为 “java/lang/Object.< init >:()V”

第5行第 2 项常量,为字段的符号引证, 格局为 {第3项常量}.{第16项常量} , 为 “TestClass.m:I”

第6行第 3 项常量,为_ w . } [ . _ |类或接2 t n @ A口的符号引证, 指向第 17* w T / 项常量,为 “Test5 w R J P [ Q @Class”

第7行第 4 项常量,为类或接口的符号引证, 指向第 18 项常量,为 “java/lang/Object”

第8行第 5 项常量,为 UTF-8 编码的字符串, 长度为1, 转化得到 “m”

第9行第 6 项常量,为 UTF-8 编码的字符串, 长度为1, 转化得到 “I”

第10行第 7 项常量X | w 8 y为 UTF-8 编码的字符串, 长度为6, 转化得到 “< init >”

第11行第 8 项常量K J ( 0 L @为 UTF-8 编码的字符串, 长度d ; U s 8为3, 转化得到 “()V”

第12行第 9 项常量,为 UTF-8 编码的字符串, 长度为4,W 6 V Y 7 转化得到 “CI G @ a * d *ode”

第13行第 10 项常量,为 UTF-8 编码的字符串, 长度q s :为15, 转化l E , } ` # # M 3得到 “LineNumbel ` ) j c u L M 5rTable”

第14行第 11 项r 4 h } B a v常量,为 UTF-8 编码的字符串, 长度为3, 转O , 7 L v f化得到 “inc”

第15行第 12 项常量,为 UTF-8 编码的字符串, 长度为3, 转化得到 “()I”

第16行第 13 项常量,为 UTF-8 编码的字符串, 长度为14, 转化得到 “SourceFiQ ) C ( 6le”

第17行/ q p / ! r l 14 项常量,为 UTF-8 编码的字符串, 长度为10, 转化得到 “TestClass.java”

第18行第 15 项常量,为字段或办法的D 7 * 6部分符号引证, 格局为 {第7项常量}:{第8项常量} “&lt. j H r Q S n R; if I 2 a D K K !nit >:()V”

第19行第 16 项常量,为字段或办法的部分符号引证,格局为 {第5项常量}:{第6项常量}, 为 z D K ; K ?“m:I”

第20行第 17 项常量,为 U@ 4 N ~TF-8 编码的字符串, 长度为9, 转化得到 “TestClass”

第21行第 18 项常量,为 UTF-8 编码的字符串, 长度为16, 转化得到 “java/lang/Object”

第22行拜访标志, 检查 拜访标志表 可知为 0x0021(0x0001|0x0020)表明这个是一个普通类,既不是接口,枚举也不是注解,被public关键字润饰但没有被~ 7 F 3 1 i y声明为final和abstract

第23行类索引, 对应 第 3 项常量, 为 “TestClassO j r ,

第24行父类索引, 对应 第 4 项常量, 为 “java/lang/Object”

第25行完成接口的数目, 0 表明没有完成任何接口

b ` c { n i26行字段的数目, 存在一个V s R k S =字段需求解析

第27行第 1 个字段, 检查 字段表 可知 拜访标志(0002)为 private, 姓名(0005)为u r M ` N p x m m, 描绘(0006)为 I, 没有特点(0000)

第28行办法的数目, 存在两个办法需求解析

第29-32行第 1 个办法, 检查 办法表 可知 拜访标志(0001)为 public, 姓名(0007)为 , 描绘(0008)为 ()V, 一个特点(0001)。因为存在一个特o * ! 1 6 8 Y点,继续检查m R b T @ 特点表。从 30 行开端解析特点 0009解析为 第 9 项常量 Code, 检查 特点表-Code特点结构表。可是发现 code 块内部分字节难以解析。这是因为部分还需求结) | 7 @ 8 g / j f合 JVM 操作字节码指令才干够。这儿先 mark 暂停h 4 H

第33-36行第 2 个办法, 检查] ] N 8 S Y 办法表 可知 拜Y f q访标志(0001)为 public, 姓名(000B)为 inc,B % M 描绘(000C)为 ()I, 一个特点(0001)。也存在一个特点。从 34 行开端解析特点 0009解析为 第 9 项常量 Code, 同上,mark 暂停。

第37行特点的数目, 存在一个特点

第38行 为 为 第 1 个特点,[ 1 7 & 000D解析为 第 13 项常量 “sourceFileY x m 3 S M + Z“, 解析 sourceFile特点得到 “ TeO # @ & = & i `stClass.java”

通过上述的解析,咱们可得到

如何读懂晦涩的 Class 文件|进阶必备

到此,根本读懂了这份字节天书。 可是 Code 特点内容仍是缺失。这时分咱们需求一份 字节码指令总表 来协助咱们进一步解析l e V k K Code 里边触及哪些指令及信息W b g O , |

JVM 操作指令有哪些

下面是 JVM` H W = x D M N 操作指令表

字节码 助记符. D r P C } I 5 指令意义
0x00 nP o I 6 Top 什么都不做
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 ico! m ] = . * # Hnst_3 将 int 型 3 推送至栈顶
0x07 iconst_4 将 int 型 4 推送至栈o T Q Z
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 dconstz 8 r D Y H ,_1 将 double 型 1 推送至栈顶
0x10 bipush 将单字节的常量(-128 – 127)推送至栈顶
0x11 sipush 将一个短整形常量常量(-32768 – 32767)推送至栈顶
0x12 ldc 将 int, float, String 型常量值从常量池中推送至栈顶
0x13 ldc_w 将 int, float, String 型常量值从常量池中推送至栈顶(宽索引)
0x14 ldc2B – H +_w 将 long 或 float 型常量值从常量池中推送至栈顶(宽索引)
0x3 C %15 iload 将指定的 int 型本地变量推送至栈顶
0x16 lload 将指定的 long 型本地变量推送至栈顶
0x17 fload 将指定的 float 型 ; p Y S 5 i本地变量推送至栈顶
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 型本地变量推送至栈顶
0xd z 5 t1f lload_1 将第二个 long 型本地; F变量推送至栈顶
0x20 lload_2 将第三_ 0 9 q L ) [ ! c个 long 型本地变量推送至栈顶
0x21 lload_3 将第四个 long 型本地变I z f量推送至栈顶
0x22 fload_0 将第一个 float 型本地变量推送至栈顶
0x23 fe % r ! ] $ j Z iload_1 将第二个 float 型本地变量推送至栈顶
0x24 fload_2 将第三个 floq o + 8 u f 0 3 [at 型本+ S i % 4 /地变量推送至栈顶O K :
0x, 5 , E25 fload_3 将第四个 float 型本地变量推送至栈顶
0x26 dloa/ / t md_0 将第一个 double 型本地变量推送至栈顶
0x27 dload_1 将第二个 double 型本地变量推送至栈顶
0x28 dload_2 将第三个 double 型本地变量推送至栈顶
0x29 dloa? K c H g j b xd_3 将第四个 double 型本地变量推送至栈顶
0x2a alo) @ S } & q O R dad_0 将第一个引证类型本地变量推送至栈顶
0x2b aload_1 将第二个引证类型本地变量推送至栈顶
0x2c aload_2 将第三个引证类型本K q 9 0 j Z地变量推送至栈顶
0x2d aload_3 将第四个引证类型本地变量推送至栈顶
0x2e iaload 将 int 型数组指定索引的值推送至栈顶
0x2f laload 将 lonM I T u 9 % Kg 型数组指定索引的值推送至栈顶
0x30 faload 将 float 型数组指定索引的值推送至栈顶
0x31 daload 将 double 型数组指定索引的值推送至栈顶
0x32 aaload 将引证型数组指定索引的值推送至栈顶
0x33 baload 将 boolean 或 bytK . ^ % ]e 型数组指定_ N h R索引的值推送至栈顶
0x34 caload 将 char 型数组指定索引的值推送至栈顶
0x35 saload 将 short 型数组指定索+ V , D * { ! r引的值z R j = f ` G推送至栈顶
0x36 istore 将栈顶 int 型数值存入指定本地变量
0x37 lstore 将栈顶 long 型数值存入指定本地变量
0x38 fstore 将栈顶 float 型数值存入指定本地变量
0x390 a S – | v 3 @ dM W * .store 将栈顶 double 型数值存入指定本地变1 A M h L
0x3a a` C D 1store 将栈顶引证型数值存入指定本5 A I | z h 2地变量
0x3b istore_0 将栈顶 int 型数值存入第一个本地变量
0x3. ? y @ ( | –c istore_1 将栈顶 int 型数值存入第二个本地y f I %变量
0x3d istore_2 将栈顶 int 型数值存入第三个本地k j .变量
0x3e istore_3 将栈顶 int 型数值存I C X M + k &入第四个本地变量
0x3f lsC j X .tore_0 将栈顶 long 型数值存入第一个本地变量
0x40 lstore_1 将栈顶 long 型数值存入第二s T ~ G | ] O v个本地变量
0x41 lstore_2 将栈顶 long 型数值存入第三个本地变量
0x42 lstore_3 将栈顶 long 型数值存入第四个本地变量
0x43 fstore_0 将栈顶 floah E tt | A 型数值存入第一个本地变量
0x44 fstore_1 将栈顶 float 型数值存入第二个本地变量
0x45 fstore_2 将栈顶 float 型数值存入第三个L u 5 ;本地变量
0x46 fstore_3 将栈顶 float 型数值存入第四个本地变量
0x47 dstore_0 将栈顶 double 型数值存入第一个本地变量
0x48 dstore_1 将栈顶 double 型数值存入第二个本地变量
0x49 dstore_2 将栈顶 doub( k j { Q Y c hle 型数值存入第三个本= ` s # x _ C地变量
0x4a dstore_3 将栈顶 double 型数值存入第四个本地变量
0x4b astore_0 将栈顶引证型数值存入第一个本地变量
0x4c astore_1 将栈顶引证型数值存入第二个本地变量
0x4d astore_2 将栈顶引证型数值存入第三个本地变量
0x4e astore_3 将栈顶引证型数值存入第四个本5 ; { P ~ K I地变量
0x4fi * u ; iastore 将栈顶 int 型数值存入指定数组q h y q的指定O ? 1 * ) l ( j索引方位
0x50 lastore 将栈顶 long 型数值存入指定数组的指定索引方位
0x51 fastore 将栈顶 float 型数值存入指定数组的指定索引方位
0x52 dastore 将栈顶 double 型数值D : ) , U存入指定数组的指定3 7 I Z J ; t |索引方位
0x53 aastore 将栈顶引证型数值存入指定数组的指定索引方位
0x54 bastore 将栈顶 boolean 或 byte 型数值存入指定数组的指定索引方位
0x55 castore 将栈顶 char 型数值存入指定数组的指定索引方位
0x56 sastore 将栈顶 short 型数值存入指定数组的指定索引方位
0x57 pop 将栈顶数值弹出(数值不能是 long 或 double 类型)
0x58q v F { pop_2 将栈顶的一个(关于 long 或 double 类型)或两个数值(关于非 long 或 dou/ ~ Z | j 7ble 的其他类型)弹出
0x59 dup 仿制栈顶数值并将仿制值压J k w r k 7 E L入栈顶
0x5a dup_x1 仿制栈顶数值并将两个仿制值压入栈顶
0x5b dup_x2 仿制栈顶数值并将三个(或两个)仿制值压入栈顶
0x5c dup_2 仿制栈顶一个(关于 long 或 doun w ! } y 2 s 3ble 类型)或两个(非 long 或 double 的其他类型)数值并将仿i T 7 P | z k d E制值压入栈顶 )
0x5d dT r I L l Y 4up_2_x1 dup_x1 指令的双倍版别
0x5e dup_2_x2 dup_x2 指令的双倍版别
0x5f swap 将栈最顶端的两个数值互换(数值不能是 long 或 double 类型)
0x60 iadd 将栈顶两 int 型数值相加并将成果压入栈顶
0x61 ladd 将栈顶两 long 型数值相加并将成果压入栈顶
0x62 fadd 将栈顶两 float 型数值相加并将成j P b 5 . – S }果压入栈顶
0x63 dadd 将栈顶两 dou~ d F Y J I * p Bble 型数值相加并将成果压入栈顶
0x64 isub 将栈顶两 int 型数值相减并将成果压入栈顶| @ ) S B
0x6( K – l m O m W [5 lsub 将栈顶两 long 型数值相减U – T N并将成果压入栈顶
0x66 fsub 将栈顶两 float 型数值相减并将成果压入栈顶
0x67 dsub 将栈顶两 double 型数值相I g f 6减并将成果压入栈顶
0x68 imul 将栈顶两 int 型数值相乘并将成果压入栈顶
0x69 lmul 将栈顶两 long 型数值相乘并m 6 K Q J .将成果压入栈顶
0x6a fmul 将栈顶两 float 型数值相乘并将成果压入栈顶
0x6b dmul 将栈顶两h b g ( double 型数值相乘并将成果压入栈顶
0x6c idiv 将栈顶两 in 9 & B unt 型数值相除并将成果压入栈顶
0x6d ldiv 将栈顶两 long 型数值相除P L F Y A , L z [并将成果压入栈顶
0x6e fdiv 将栈顶两 float 型数值相除并将成果压入栈顶
0x6f ddiv 将栈Q { / o & M 9顶两 double 型数值相除并将成果压入栈顶
0x70 irem 将栈顶两 int 型数值作取模运算并将成果压入栈O 0 | d 7
0x71 lrem 将栈顶两 long 型数值作/ D / } v [取模运算并将成果压入栈顶
0x72 frem 将栈顶两 float 型数 A 9 a & S 9 ^ 值作取模运算并将成果压入栈顶
0x73 drem 将栈顶两 double 型数值作取模运算并将成果压入栈顶
0x74 ineg 将栈顶Q ( n两 int 型数值作负并将成果压入栈顶
0x75 lneg 将栈顶两 long 型数值作负并将成果压入栈顶
0x76 fneg 将栈顶两 float 型T J t = ( 7 G Y z数值作负并将成果压入栈顶
0x77 dneg 将栈顶两 double 型数值作负并将成果t j w {压入栈顶( r Z + o [ #
0x78 ishlP 6 v D A o f 将栈顶两 int 型数值左移位指定位数并将成果压入栈顶
0x79 lshl 将栈顶两 long 型数值左移位指定位数并将成果压入栈顶
0x7a ishr 将栈顶两 int 型数值右(带符号)移位指定位数并将成果压入栈顶
0x7b lshr 将栈顶两 long 型数值右(带符号)移位指定位数并将成果压入栈顶
0x7c iushr 将栈顶两 iz Z 8 J Y v G /n| { ^ Y 2 x 6t 型数值右(无符号)移位指定位数并将成果压入栈顶
0x7d lushr 将栈顶两 long 型数值右(无符号)移位指定位数并将成果压入栈顶
0x7e ia6 @ I Q %nd 将栈顶两 int 型数值作 “按位与” 并将成果压入栈顶
0x7fj $ r 9 ; land 将栈顶两 l# . 3 _ S { } xong 型数值作 “按位S L Z s c与” 并将成果压入栈顶
0x80 ior 将栈顶两p T X n Z ( t int 型数值作 “按位或” 并将成果压入栈顶
0x81 lor 将栈顶两 long 型数值作 “按位或” 并将成果压入栈顶
0x82 ixor 将栈顶两 int 型数值作 “按位异= s Q V或” 并将成果压入栈顶
0x83 lxor 将栈顶两 long 型数值作 “按位异或” 并将成果压入栈顶
0x84 iincw K $ g 直接对 int 型变量增加指定值(如i++, i–, i+=2等)
0x85 i2l 将栈顶 int 型数{ * y p 7 H c 8 `值强制转成 long 型数值并将成果压入栈顶
0x86 i2f 将栈顶 int 型数值强制转成 float 型数值并将成果压入栈顶
0x87 i2d 将栈顶 int 型数值强制转成 double 型数值并将成果压入栈顶
0x88 l2i 将栈顶 long 型数值强制转成 int 型数值并将成果压入栈顶
0x89 l2f 将栈顶 long 型数值强制转成 flox { ! xat 型数4 p Z c t = ; ) 1值并将成果压入栈顶
0x8a l2d 将栈顶 long 型数值强制转成 double 型数值并将成果压入栈顶
0x8b f2i 将栈顶 float 型数值强制转成 int 型数值并将成7 o b / y J果压入栈顶
0x8c f2l 将栈顶1 . float 型V s w K )数值强制转成 long 型数值并将成果压入栈顶
0x8d f2d 将栈顶 float 型数值强制转成 double 型数值并将成果压入栈顶
0x8e d2G N ) Y Y # i zi 将栈顶 double 型数值强制转成 int 型数值并将成果压入栈顶
0x8f d2l 将栈顶c t Q 2 double 型数值强制转成 long 型数值并将成果压入栈顶
0x90 d2f 将栈顶 double 型数值强制C # e G转成 float 型数值并将成果压入栈顶T # M ) 8 ~ [
0x91k i I ( i2b 将栈顶 int 型数值强制转成 byte 型数值并将成果压入栈顶
0x92 i2c 将栈顶 int 型数值强制转成 char 型数值并将成果R M T f压入栈顶
0x93 i2s 将栈顶e = / int 型数值强制转成 short 型数值并将成果压入栈顶
0x94 lcmp 比较栈顶两 long 型数值的巨细,并将成果(1, 0 或 -1)压入栈顶
0x95 fcmpl 比较栈顶两 float 型数值的巨细,并将成果(1,e x G $ 0 或[ L k C W K G J -1)压入栈顶; 当其间一个数值为 “NaN” 时,将 -1 压入栈顶
0x96 fcmpg 比较栈顶两 float 型数值Y % ; 的巨细,并将成果(1, 0 或 -1)压入栈顶; 当其间一个数值为 “NaN” 时,将 1 压入栈顶
0x97 dcmpl 比较栈顶O N } l T R l A两 double 型数值的巨细,并将成果(1, 0 或 -1)压入栈顶; 当其间E – G ]一个数值为 “NaN” 时,将h r W 1 A G -1 压入栈顶
0x98 dc4 [ t h ~ V ^ vmpg 比较栈顶两 double 型数; . l值的巨细,并将成果(1, 0 或 -1)压入栈顶; 当其间一个数值为 “NaN” 时,将 1 压入栈顶
0x99 ifeg 当栈顶 int 型数值等于 0 时跳转
0x9a ifne 当栈顶 int 型数值不] h & 9等于 0 时跳转
0x9b iflt 当栈顶 int 型数值小于 0 时跳转
0x9c ifge 当栈顶 int 型数值大于或等于 0 时跳转
0x. @ D9d ifgt 当栈顶 int 型数值大于 0 时跳转
0x9p + x 8e ifle5 : X ^ N } g b 当栈顶 int 型数值小于或等于 0 时跳转
0x9f if_icmpeq 比较栈顶两 int 型数值的巨细,当成果等于 0 时跳转@ h K % 2 e
0xa0 if_icmpD K ne 比较栈顶两 int 型数m 7 + / ?值的巨细[ 6 7 k 4 ) f + 8,当成果. j ` o & z不等于 0 时跳转
0xa1 if_icmplt 比较栈顶两: * K K r i ] int 型U K Y :数值的巨细,当成果小于 0 时跳转
0xa2 if_icmpge 比较栈顶两 int 型数值的巨细,当成果大于或等于 0 时跳转
0xa3 if_icmpgt 比较栈顶两 inG ? w # x 9t 型数值的巨细,当成果大于 0 时跳转
0xa4 if_icmple 比较栈顶两 int 型数值的巨细,当成果小于或等于 0 时跳{ & I T j $ j
0xa5 if_icmpeq 比较栈顶两引证型数值,当成果持平时跳转
0xa6 if_icmpnc 比较栈顶两引证型数值,当成果不持平时跳转
0xa7 goto 无条件跳转
0xa8 jsr 跳转至指定的 16 位 offset 方位,并将o X 4 @ . @ & jsr 的下一条指令地址压入栈顶
0xa9 ret 回来至本地变量指定的 index 的指令方位(一般与 jsr 或 jsr_w 联合运用)
0xaa tableswitch 用于 switch 条件跳转, case 值接连(可变长度指令)
0xab lookupswitch 用于 switch 条件跳| d v x v g H转, case 值连不续(可变长度指令)
0xac ireturn 从当时办法回来 int
0xad lreturn 从当时办法回来 long
0xae freturn 从当时办法回来 float
0xaf dreturn 从当时办法回来 double
0xb0 areturn 从当时办法回来目标引证
0xb1 return 从当时办法回来 void
0xb2 getstatic 获取指定类的静态域,并将其值压入栈顶
0xb3 putstatic 为指定的类的静态域赋值
0xb4 getfield 获取指定类的实例域j b y ),并将其值压入栈顶
0xb5 putfield 为指定的类的实例域赋值
0xb6 invoq 4 w – M m ] ckevirtual ~ $ ` ( = N n 3用实例办法
0xb7 invokespecial 调用超类构造办{ V e 4 g k法, 实例初始化办法,私有办法
0xb8 invokestatic 调用静态办法
0xb9 invokeinterface 调用接口办法
0xba invokedynamic 调用动态办法
0xbb new 创立一个目标,并将其引证值压入栈顶
0xbc newarray 创立一个指定的原始类型(如 int, float等)的数组,并将其引证值压入栈顶
0xbd anewarray 创立一个引证型(如 类,接口,/ N 数组)的数2 p = 6 6 ~ O O组,并将其引证值压入栈顶
0xbe arraylength 获得数组的长度值并压入栈顶
0xbf athrow 将栈顶的反常抛出
0xc0 checkcast 查验类型转化, 查验未通过将抛出 ClassCastException
0xc1 instanceof 查验目标是否时R ^ S指定类3 O s a D @ %的实例, 假如是, 则将 1 压入栈顶,否则将 0 压入栈顶
0xc2 monitorenter 获得目标的锁,用于同步办法或同步块
0xc3 monitorexit 开释目标的锁,用于同步办法或同步块
0xc4 wide 扩展本地变量的宽度
0xc5 multianewarray 创立指定类型和指定维度的多维数组(履行该指令时,操作栈中有必要包含各维度的长度值),# o )并将其引证值p * Y压入栈B + T
0xc6 ifnull 为 null 时跳转
0xc7 ifnonnull 不为 null 时跳转
0xc8 goto_w 无条件跳转(宽索引)
0xc9 jsr_w 跳转至指定的 32 位 offset 方位,并将 jsr_w 的下一条指令地址压入栈顶

上述表格内容更为详细的信息R R = )可参考 官方JVM指令文档,除k r 5 Y I q z Q m上述表外,还需求认识一些数据类型及转化对应规矩,一起再对上述指令的运用场景做一些总结区分。

数据类型在指令T n A q中的转化

数据类型Z o L = A 5 byte shor^ & mt int long float double char reference
简化$ | o * d [ : P转化 b s i l f d c a

指令集支撑的数据类型

下面表格中T+指令构成 opK v s ~ m Bcode, T 为上面表格各c Y 8 – 9 ` 0 B数据Z c 1 l 4 } 8 ^ 1类型的简化转化。

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 ist! U ~ A ( ; H 3or/ | k U ; / te 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 lU s _ . u i w Eadd fadd dadd
Tsub isub lsub fsub dsub
TmF i * hul imul lmul fmul dmul
Tdiv idiv ldiv fdiq X @v ddiv
Trem irem lrem frem drem
Tneg iH – n 1 % j R ]neg lneg fneg dneg
Tshl ishl lshl
Tshr i6 M F k hshr lshr
Tushr iushr lushr
Tand iand land
Tor ior lq * B T 7 ! @ Hor
Txor ixor lxor
i2T i2b i2s i2l i2f i2d
l2T l2i l2f l2d
f2T f2i f2l f2d
d2T d2i dK ` x2l d2f
Tcmp l# M a `cmp
Tcml fcml dcml
Tcmpg fcmpg dcmpg
if_TcmpOP if_icmpOP if_acopOP
Treturn ireturn lreturn fre/ + j C – – j 5 turn dreturn areturn

大部分指令没有支撑byte,cP 5 2har和short乃至是boolean,编译器会在编译器或许运行期把这类数据扩展为 int类型数据。

加载/存储指令

加载/存储@ a ~ E w ?指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。

  • 将一个局部变量加载到操作栈: Tload} u , I f Tload_n 后者表明是一组指令。R ] y H 6 e
  • 将一个数值从操作数栈存储– t j , ( m到局部变量表: TstoreTstore_n{ ) z A A 后者表明是一组指令。
  • 将一个常量加载到操作数栈: TipushO r b ? b a DldcT_cog : 3 fnst
  • 扩充局部变量表的拜访索引指令:wide

运算指令

对操作数栈的数值进行运算之后把成果从头存入操作栈栈顶。

  • 加法指令 Tadd
  • 减法指令 Tsub
  • 乘法指令 Tmul
  • 除法指令 Tdiv
  • 求余指令 Trem
  • 取反指令 Tneg
  • 位移指令 Tsa q = B Yhl, Tshr, Tuj j { H ] :shr
  • 按位或指令 Tor
  • 按位与指令 Tand
  • 按位异或指令 Txor
  • 局部变量自增指令 T+ L _ Einc
  • 比较指令 Tcmpg ,Tcmpl

类型转化指令

类型转化指令用于将两种不同的数值类型进行相互转化,这种转化操作一般用于完成用户代码中的显式转化操作,或许用于处 3 Z G W N理字节码指令会集数据类型相关指令无法与数据类型一一对应的问题。

  • int类型转其他 i2T
  • long类型转} R 8 v # 2 m z其他 l2T
  • f+ 5 –loat类型转其他 f2T
  • double类型转其他 d2T

目标创立与拜访指令

尽管类实例和数组都是目标,但Java虚拟机对类实例v X $ / & E P ! .和数组的创立与操作运用了不同的字节码指令。

  • 创立类实例 new
  • 创立数E D 4 r }newarray, anewarray, mum k | ? r u (lt5 m v Rianewarray
  • 拜访类变量和实例变量 ge7 g W Y F j Ptfield, putfieldgetstaticputstatic
  • 把一个数组元素加载到操作数栈 Taload
  • 将一个操作数栈的值存储到数组元素中 Tastore
  • 取数组长度的指令 arraylength
  • 检查类实例类型 instanceof, checkcast

操作数栈办理指令

* 将操作数栈栈顶一个或许两个元素出栈 `p~ g } 0 X g Hop`,`pop2`
* 仿制栈顶一个或两个数值并将仿制值从头压入栈顶 `dup`,`dup2`, `dup_x1`,`dup2_ D D R F I_x1`,`dup_x2`,`dup2_x2`
* 将v 4 ) @ S T  q g栈最顶端两个数值互换 `swap`

操控转移指令

让虚拟机能够有条件或许无条件地从特定方位指令履行程序而不是在操控转移指令的下一条指令履行程序。

  • 条件分支 ifeq, is Q P M nfit, ifle, ifne, ifgt, ifge, if s &ull, ifnonnull, if_icmpeq, if_icmpne, if_icmplt, if_icmpgt, if_icm_ Y b & dple, if_icmpge, if_acmpeq, if_Z v @ r Q jacmpne
  • 复合条件分支 tabl1 ? { S x ; y & Meswitch, lookupswitch
  • 无条件分支 goto, goto_w, jsr, jsr_w, ret

办法调用和回来指令

  • 调用目标的实例办法 invokevirtual,依据目标的实际类型进行分0 2 U ! :
  • 调用接口办法 invokeinterface , 会在运行时查找一个完成了这个接口的办法的目标,找到合适的办法进行调用
  • 调用一些需求特别处理的实例办法7 0 f x k & ! G invokeY - { q 5specialT * H E Q Y ) 7 0,包含实例初始化办法,私有办法和父类办法
  • 调用类办法 invokestatic 用于调用static办( – h s
  • 运行时动; ` f q ^ @态解析处调用点限定符所引证的办法并履行I O X V A 7该办法 invokedynamic ,区别于前面4{ @ 8 n o条指令,它们都在固化在j( x ) } cvm内部,而该指令的分派逻辑是由用户所设定的引导办法决议的。

反常处理指令

athrow 指令用于完成显式抛出反常(throw句子)的操作,除了用throw句子之外,JVM还规定在运行时会在其他L ; 6 _ JVM指令检测到反常状况的时分主动抛出。比方当除数为0的时分,JVM会在 idivl X [ { Cldiv 中抛出 ArithmeticException 反常。

同步指令

JVM的同步有一下场景,都是运用管程(Monitor)来支撑

  • 办法级的同步,不需求字节码操控,完成于办法调用和回来操作志宏。从办法表中 ACC_SYNCHRONIZET ( % = PD 得到一个办法是否是同步,假如被设置,则履行线程需求先持有管程才干履行,履行完之后开释管程。
  • 办法内P 5 D e Q 5 5 + =部一段指令序R = v列的同步,由synchronized和指令monito+ b W b = o b Rrentermonito% 0 k Y j Q [ ( |rexit来支撑synchZ 4 j ~ `ronized共同完成

咱们的 非常简略,实际上用不到这么多指令~ % i的,其他的可备份用于查询。

指令集辅助解析 Code

有了上述指令集协F K t助及膝,回到 从头认识字节天书 中的第 2. H R z @ p _ s9-36 行内容从头解析。

3 & d p 829-; Z c v32行第 1 个办法, 检查 办法表 可知 拜访标志(0001)为 public, 姓名(0007)为 , 描绘(0008)为 ()V, 一个特点(0001)。因为存在一个特点,继续检查 特Y x @ R y K点表。从 30 行开端解析特点 0009解析为 第 9 项常量 Code, 检查 特点表-Code特点结构表 及结合指令会集操作符信息, Code 特点终究的内容如下。(看到这儿,你应该尝试过一遍哦 )

如何读懂晦涩的 Class 文件|进阶必备

第33-36行第 2 个办法, 检查 办法表 可知 拜访标志(0001)为 public, 姓名(000B)为 ig m D ?nc, 描绘(000C)为 ()I, 一个特点[ F , g V r j(0001)。也存在一个特点。从 34 行开端0 ( & % D I U j c解析特点 0009解析为 第 9 项常量 Code, 内容如下。

如何读懂晦涩的 Class 文件|进阶必备

至此,.class 文件的内容根本承认可知。

如何读懂晦涩的 Class 文件|进阶必备

u 1 j 2 D %了验证咱们的思路是否正确,能够通过 javap 检查 TestClass.class 的结构来进行比照。

如何读懂晦涩的 Class 文件|进阶必备

除了 javap 帮咱们做了格局化的作业外,也是o k 3 } 9 =依照咱们剖析字节码的逻{ n + J | N辑来进行内容的输出, 感兴趣的同伴能够检查 javap 内部完成。

当你读懂 Class 文件之后,你就能够进一步做很多作业了,比方借助 ASJ t A 0 k } ` OM 框架侵略 gradle 构建流程注入静态代码等,更多场景等你发掘。

假如文章对您有协助,欢迎点赞评论支7 + p y ?撑,若其间有错误,欢迎评论指出哦。

发表评论

提供最优质的资源集合

立即查看 了解详情