一步一步拆解字节码(.class)文件

为了方便理解,本篇博客将围绕一个例子展开

目录

一. magic(魔数)

二. minor version(次版本号)和major_ version(主版本号)

三. constant pool count(常量个数)和constant pool(常量池表)

四. access_ flags(类的访问控制权限)

五. this_ class(类名)和super_ class(父类名)

六. interfaces_count(接口个黎)和interfaces(接口名)

七. fields_ count(域个数)和fields(域的表)

八. methods_ count(方法的个数)和methods(方法表)

九. attributes_ count(附加属性的个数)和attributes(附加属性的表)


例 day02.ByteCode.java:

一步一步拆解字节码(.class)文件

IDEA中自带查看字节码信息工具,该字节码文件的魔数、版本号、常量池、类信息、类的构造方法、 类中的方法信息、类变量与成员变量等信息。

一步一步拆解字节码(.class)文件

如果想要查看其十六进制字节码文件,需要下载WinHex工具,用它打开:

一步一步拆解字节码(.class)文件

接下来咱们详细分析这些二进制到底是什么东西。

字节码中携带信息顺序如下图(类型中un代表占位n个字节,类型中包含info的,说明该类型中包含许多字段信息,会有相应的表描述这些字段):

一步一步拆解字节码(.class)文件

一. magic(魔数)

       所有合法的.class字节码文件的前4个字节都是魔数,魔数值为固定值: 0xCAFEBABE,即咖啡宝贝之意。

二. minor version(次版本号)和major_ version(主版本号)

      魔数之后的4个字节为版本信息,前两个字节表示minor version (次版本号) ,后两个字节表示major version (主版本号) .这里的版本号为00 00 00 34,换算成十进制,表示次版本号为0,主版本号为52。52对应的jdk版本号为: 1.8.0,同理可推51对应1.7.0。 可以通过java -version命令来验证这一 点。

三. constant pool count(常量个数)和constant pool(常量池表)

       紧接着主版本号之后的就是常量池入口。一个Java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是Class文件的资源仓库,比如说Java类中定义的方法与变量信息,都是存储在常量池中。常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,Java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符等。

       常量池的总体结构: Java类所对应的常量池主要由常池数量与常量池数组(常量表)这两部分共同构成。 常量池数量紧跟在主版本号后面,占据2个字节;常量池数组则紧跟在常量池数量之后。常量池数组与般的数组不同的是, 常量池数组中不同的元素的类型、结构都是不同的,长度当然也就不同;但是,每种元素的第一个数据都是一个u1类型,该字节是个标志位,占据1个字节。JVM在解析常量池时,会根据这个u1类型来获取元素的具体类型。值得注意的是,常量池数组中元素的个数=常量池数- 1 (其中0暂时不使用),目的是满足某些常池索引值的数据在特定情况下需要表达 不引用任何个常量池的含义;根本原因在于, 索引为0也是个常量(保留常量) ,只不过它不位于常量表中,这个常量就对应nu11值;所以,常池的索引从1而非0开始。

一步一步拆解字节码(.class)文件

                                                                             图 3-1 常量查询参照表

一步一步拆解字节码(.class)文件

常量池长度:00 18 即常量个数有24-1 = 23个

常量1:

0A 00 04 00 14,第一个字节时tag值,0A = 10,所以tag = 10,对应CONSTANT_Methodref_info常量

该常量有三部分组成:tag(U1),index(U2),index(U2),后面是Un就代表占n个字节

所以声明方法的类描述符(结合图3 -1)的索引值为 00 04即为4

名称及类型描述符(结合图3 - 1)的索引值为00 14即为20

该常量的值就等于常量4与常量20的值“相加”。

常量2:

tag值为09,在图3-1中查询tag等于9,即CONSTANT_Fieldref_info常量

后面的第一个index字段占位2个字节,00 03,因为是index类型所以,找到的3是常量在常量池中的位置

同理第二个index字段是00 15,该常量是第三个常量和第二十一个常量相加的结果

需要注意的是如果类型为index指的是常量池中的常量位置,如果是byte,那么将相应bytes转换为英文字母即为结果

同理二十三个常量如下图:

一步一步拆解字节码(.class)文件

四. access_ flags(类的访问控制权限)

一步一步拆解字节码(.class)文件

                                                              图 4-1 访问控制权限图

一步一步拆解字节码(.class)文件

直接查表,00 21查表得类的访问权限为public super;

JDK1.2之后编译出来的类的super标志都必须为真。

五. this_ class(类名)和super_ class(父类名)

一步一步拆解字节码(.class)文件

类名:00 03即查找下标为3的常量,即本类day02/ByteCode

父类名:00 04即java/lang/Object

六. interfaces_count(接口个黎)和interfaces(接口名)

一步一步拆解字节码(.class)文件

接口个数:00 00,所以接口名不占空间

七. fields_ count(域个数)和fields(域的表)

一步一步拆解字节码(.class)文件

域个数:00 01 只有一个成员变量:

一步一步拆解字节码(.class)文件

域的表:

访问控制类型:00 02通过图 4-1 访问控制权限图查询即private类型

变量名下标:00 05,在常量池中查找即a

变量描述信息:00 06,在变量池中查找即I

变量附加信息个数:00 00

所以attribute_info去域为空,不占位

八. methods_ count(方法的个数)和methods(方法表)

一步一步拆解字节码(.class)文件

方法个数:00 03即三个,分别是默认构造方法,get方法,set方法

一步一步拆解字节码(.class)文件

方法1(构造方法):

访问权限控制符(access_flag):00 01 即public类型

方法名下标(name_index):00 07 ,在常量池中找下标为7的常量,即<init>,它的意思是代表构造方法

描述下标(descriptor_index):00 08,即()V,代表无参且返回值为void类型

属性个数(attributes_count):00 01代表只有一个

属性信息:

属性信息中,前两个字节代表属性名在常量池中的下标,再根据属性名查相应的表

     属性名下标:00 09,查找得:Code,下图是Code属性表相关信息

一步一步拆解字节码(.class)文件

 

atrribute_name_index已经查找过了

    属性信息长度:即不包括attribute_name_index和atrribute_length的Code_attribute总长度,00 00 00 38计算得共占56个字节

    maxstack:表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,00 02计算得最大深度为2

    max_locals:表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量,00 01即共创建1个参数

    code_length:code length表示该方法所包含的字节码的字节数以及具体的指令码,00 00 00 0A即长度为10个字节

    code[code_length]:该部分包含了一些助记符,形式如下:

一步一步拆解字节码(.class)文件

 

    2A:代表aload_0,下面是官网文档,代表将其推至栈顶,官网地址

一步一步拆解字节码(.class)文件

 

    同理:

    B7:代表invokespecial,00 01是它携带的参数在常量池中的位置:

一步一步拆解字节码(.class)文件是父类的构造方法,官网地址

 

    2A:aload_0 ,官网地址

    04:iconst_1,官网地址

    B5:putField,后面的00 02是常量池中的位置,day02/ByteCode.a:I,官网地址

    B1:return,官网地址

    该方法的意思是:在Object的构造方法中将day02/ByteCode中的成员变量a赋值为1

    exception_table_length:异常表的长度,这里没有异常处理,所以为00 00,所以exception_table字段不会显示了

    attributes_count:00 02,就是两个属性表

        attribute_info表中数据各字段如下:

一步一步拆解字节码(.class)文件

 

    先看第一个:attribute_name_index:00 0A在常量池中查找得:LineNumberTable即行号表,格式如下:

一步一步拆解字节码(.class)文件

 

        这里要说明一下:attribute_info表是LineNumberTable_attribute表的父表,字表中把父表的info字段实例化啦

        接着看:

        attribute_length:00 02即其中只有两个属性

          第一个:start_pc:偏移量,00 00,line_number:实际行号为00 03

          第二个:start_pc:偏移量,00 04,line_number:实际行号,00 04

        因为现在还在构造方法中,在编译时,会自动在类名后创建构造方法,所以构造方法在第三行,又因为a在第四行定义赋值,所以第二个属性实际行号是4

    再看第二个:attribute_name_index:00 0B,在常量池中查找第11个变量得:LocalVariableTable,详情见下表,

 

一步一步拆解字节码(.class)文件

        attribute_length:00 00 00 0C:长度为12,指的是局部变量表长度字段(U2)和局部变量表的总长

        local_variable_table_length:00 01说明只有一个变量,你可能觉得构造方法中没有定义局部变量,但是请你接着看

        这一个变量的local_variable分为五部分:

          start_pc:程序计数器起始值,00 00

          length:索引长度,00 0A,长度为10

          name_index:名称索引,00 0C,查常量池得this

          descriptor_index:描述符索引,00 0D,查常量池得Lday02/ByteCode;即this是day02.ByteCode类型的对象

          index:变量下标,00 00即第0个

        这里可以看出每个非静态方法中都隐式传入一个this作为局部变量以供使用

其它两个方法亦然;这里不做赘述。

九. attributes_ count(附加属性的个数)和attributes(附加属性的表)

一步一步拆解字节码(.class)文件

attributes_count:00 01代表只有1个属性

attribute_index:00 12,在常量池中查找,得SourceFile,源文件

attribute_file_length:00 00 00 02,表示文件位置下标占位2个字节

attribute_file_index:00 13,在常量池中,查找得ByteCode.java

表示有源文件是ByteCode.java。

用IDEA的可以添加一个jclasslib插件,方便使用

一步一步拆解字节码(.class)文件

第一次写那么长的文章,错误之处,请多批评指教。