Class文件解读

内容导航

摘要

您好! 本篇旨在借助Hello World小程序讲解class文件结构,希望给您在您理解class结构的路上推波助澜。如有不足,请不吝赐教。

环境

Java:JDK8
JVM Doc:Java SE 8
class查看器:winhex

实践

源代码

喜闻乐见Hello World,源码如下:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

对应class文件

javac编译HelloWorld.java生成HelloWorld.class。其过程就不废话
在这里插入图片描述

class文件查看

1、用上面列出的工具winhex打开HelloWorld.class,如下:
HelloWorld对应class内容
说明,图片是以1字节为基本单位的十六进制表示的class内容,class实际还是二进制存储的,只不过为了容易查看,将二进制转为十六进制而已。

2、使用如下javap命令查看HelloClass.class结构:
在这里插入图片描述
显示内容如下:
javap展示1
javap展示2
此为虚拟机解读出来的结果。我们要做的就是模拟虚拟机解读过程。

class整体结构

参照官方虚拟机规范文档可知,class文件伪结构如下:

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

不一一说明,直接看下图。再有不明白,就看官方虚拟机规范文档.
class文件整体结构示意图
图片说明:
方格中有文字的都是所占字节长度不确定,无文字的都是占1字节(魔数也在其列,方格中的只是作为固定值展示而已)。

class解读征途(人工)

记住一点,class文件是一串严格有序无分隔符无对齐符二进制流

1、魔数

长度:4字节
值:0xCAFEBABE
对应位置:
在这里插入图片描述
说明:固定值,为了表示此文件为一个class文件。

2、次版本号

长度:2字节
值:0x0000
对应位置:
在这里插入图片描述
对应javap展示结果:
在这里插入图片描述

3、主版本号

长度:2字节
值:0x0034,对应十进制52
对应位置:
在这里插入图片描述
说明:与上面次版本号组合标志class文件的版本。如主版本号为M,次版本号为m,则class文件版本为 M.m。
对应javap展示结果:在这里插入图片描述

4、常量池计数

长度:2字节
值:0x001D
对应位置:
在这里插入图片描述
说明:十六进制换算成十进制为29,对应数组索引为,0 ~ 28,但索引0位置是虚拟机预留的,不可使用。故索引范围实际为1 ~ 28,表示容量池中仅有28个常量元素。

5、常量信息数组

常量是一个包含多个无符号数和项的复合数据结构。常量的通用伪结构模板如下:

cp_info {
    u1 tag;
    u1 info[];
}

因结构中未指定info[]长度,所以不能直接确定一个常量的具体长度。唯一可确定的是常量都是以一个字节的tag常量类型开始,所以只能先确定常量类型,再确定具体的伪结构。

5.1、常量1

5.1.1、常量类型

长度:1字节
值:0x0A
对应位置:
在这里插入图片描述
说明:十六进制转为十进制,对应值为10;参照常量类型表
常量类型表
可知,此常量类型为CONSTANT_Methodref,对应的实际伪结构如下:

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

所以常量1对应数据位置:
在这里插入图片描述
三个红色小框分别对应伪结构的三项:tag、class_index、name_and_type_index。

项目十六进制值十进制值解析值
tag0x0A10CONSTANT_Methodref
class_index0x00066
name_and_type_index0x000F15

对应javap结果:
在这里插入图片描述

5.2、常量28

因篇幅问题,省略其它常量的分析过程,直接最后一个常量。
常量28对应位置
说明:由于常量类型0x01,查常量类型表可知该常量类型为:CONSTANT_Utf8,用来表示常量字符串值。而其对应伪结构如下:

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

关于tag上面已经说过;length,占2字节,对应下面字节数组包含的元素数量。u1 bytes[length],表示字节数组,也即,下面是连续length个各占一字节的无符号数,最终应该是个字符串。

项目十六进制值十进制值解析值
tag0x011CONSTANT_Utf8
length0x000615
bytes[length]0x28、0x4C、0x6A、0x61、0x76、0x61、0x2F、0x6C、0x61、0x6E、0x67、0x2F、0x53、0x74、0x72、0x69、0x6E、0x67、0x3B、0x29、0x56(Ljava/lang/String;)V

对应位置:
在这里插入图片描述
说明:关于字节数组bytes[]转字符串,可以查询ASCII对照表,可以自己试一试,这里不累赘。

到此,常量池中的内容分析结束,让我们查看一下常量池全内容:
在这里插入图片描述

6、访问标志

长度:2字节
值:0x0021
对应位置:
在这里插入图片描述
说明:
2字节中各bit位对应标志
在这里插入图片描述
0x0021转为二进制为:
对应二进制图
对照上图可知,分别对应ACC_SUPER、ACC_PUBLIC
对应javap展示结果:
在这里插入图片描述

7、类索引

长度:2字节
值:0x0005
对应位置:
在这里插入图片描述
说明:此值为常量池索引值,此索引对应的常量的类型必须为CONSTANT_Class
对应javap展示结果:
在这里插入图片描述

8、父类索引

长度:2字节
值:0x0006
对应位置:
在这里插入图片描述
对应javap展示结果:
在这里插入图片描述
说明:此值为常量池索引值,非0索引对应的常量的类型必须为CONSTANT_Class,索引0则表示父类为Object。源码中HelloWorld并没有显式继承父类,这里确出现父类java.lang.Object。正好应证了java.lang.Object是所有类(除去Object)的父类。

9、直接接口计数器

长度:2字节
值:0x0000
对应位置:
接口计数器对应位置
说明:十进制数为0,表示没有显式实现接口。官方对此项的说明为:
The value of the interfaces_count item gives the number of direct superinterfaces of this class or interface type.
我理解为:该类或接口类型显式实现或继承的接口数量。

10、接口信息表

因为接口计数器指定值为0,索引接口表为空

11、字段计数器

长度:2字节
值:0x0000
对应位置:
在这里插入图片描述
说明:十进制为0,表示没有字段

12、字段信息表

因字段计数器指定值为0,此部分为空

13、方法计数器

长度:2字节
值:0x0002
对应位置:
在这里插入图片描述
说明:十进制值为2,有2个方法。

14、方法信息表

方法的伪结构如下:

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

14.1、方法1

14.1.1、访问标志(access_flags)

长度:2字节
值:0x0001
对应位置:
在这里插入图片描述
说明:指定十进制值为1,查方法访问标志表
在这里插入图片描述
可知,对应ACC_PUBLIC
对应javap展示结果:
在这里插入图片描述

14.1.2、方法名索引值(name_index)

长度:2
值:0x0007
对应位置:
在这里插入图片描述
说明:对应常量池索引值7所存的常量,该常量类型必须为CONSTANT_Utf8
对应javap展示结果:
在这里插入图片描述

14.1.3、描述索引(descriptor_index)

长度:2字节
值:0x0008
对应位置:
在这里插入图片描述
说明:对应常量池索引值8所存的常量,该常量类型必须为CONSTANT_Utf8
对应javap展示结果:
在这里插入图片描述

14.1.4、其他属性的数量(attributes_count)

长度:2字节
值:0x0001
对应位置:
在这里插入图片描述
说明:此值表示该方法还有一个属性说明

14.1.5、其他属性信息表

其它属性的伪结构如下:

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info [attribute_length];
}
14.1.5.1、其它属性信息1
14.1.5.1.1、属性名索引值

长度:2字节
值:0x0009
对应位置:
在这里插入图片描述
说明:对应常量池索引9对应的常量,该常量类型必须为CONSTANT_Utf8
对应javap展示结果:
在这里插入图片描述

14.1.5.1.2、属性长度

长度:4字节
值:0x0000001D
对应位置:
在这里插入图片描述

14.1.5.1.3、 属性表

根据上面属性长度所指示的值为29,表示接下来29个连续的各占1字节的均为属性信息。
长度:29字节
值:0x00、0x01、0x00、0x01、0x00、0x00、0x00、0x05、0x2A、0xB7、0x00、0x01、0xB1、0x00、0x00、0x00、0x01、0x00、0x0A、0x00、0x00、0x00、0x06、0x00、0x01、0x00、0x00、0x00、0x01
对应位置:
在这里插入图片描述
说明:根据属性名code,查询官网文档可知,该code实际伪结构如下:

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

则此部分内容应该对应伪结构中从u2 max_stack 开始的所有项。具体我会在对第二个方法解读时候详解。

14.2、方法2

14.2.1、访问标志(access_flags)

长度:2字节
值:0x0009
对应位置:
在这里插入图片描述
说明:指定十进制值为9,查方法访问标志表
在这里插入图片描述
可知,对应ACC_PUBLIC、ACC_STATIC
对应javap展示结果:
在这里插入图片描述

14.2.2、方法名索引值(name_index)

长度:2
值:0x0007
对应位置:在这里插入图片描述
说明:对应常量池索引值11所存的常量,该常量类型必须为CONSTANT_Utf8
对应javap展示结果:
在这里插入图片描述

14.2.3、描述索引(descriptor_index)

长度:2字节
值:0x0008
对应位置:
在这里插入图片描述
说明:对应常量池索引值12所存的常量,该常量类型必须为CONSTANT_Utf8
对应javap展示结果:
在这里插入图片描述
在这里插入图片描述

14.2.4、其他属性的数量(attributes_count)

长度:2字节
值:0x0001
对应位置:
在这里插入图片描述
说明:此值表示该方法还有一个属性说明

14.2.5、其他属性信息表

其它属性的通用伪结构如下:

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info [attribute_length];
}
14.2.5.1、其它属性信息1
14.2.5.1.1、属性名索引值

长度:2字节
值:0x0009
对应位置:
在这里插入图片描述
说明:对应常量池索引9对应的常量,该常量类型必须为CONSTANT_Utf8
对应javap展示结果:
在这里插入图片描述
则该属性实际伪结构如下:

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

与方法1一致,这里就直接按实际伪结构一一解读。

14.2.5.1.2、属性长度

长度:4字节
值:0x00000025
对应位置:
在这里插入图片描述

14.2.5.1.3、 操作数堆栈的最大深度

长度:2字节
值:0x0002
对应位置:
在这里插入图片描述
对应javap展示结果:
在这里插入图片描述

14.2.5.1.4、局部变量的数量

长度:2字节
值:0x0001
对应位置:
在这里插入图片描述
说明:包括用于在调用该方法时将参数传递给该方法的局部变量。对于long或double类型值的最大局部变量索引是 max_locals - 2。任何其他类型的值的最大局部变量索引为max_locals - 1。
对应javap展示结果:
在这里插入图片描述

14.2.5.1.5、指令数组长度

长度:4字节
值:0x00000009
对应位置:
在这里插入图片描述

14.2.5.1.6、指令信息表

一个字节代表一条指令,可参照官方指令说明

14.2.5.1.6.1、指令信息1

长度:1字节
值:0xB2
对应位置:
在这里插入图片描述
查询官方指令对照表可知,表示指令 getstatic
在这里插入图片描述
对应javap展示结果:
在这里插入图片描述
余下指令查询结果如下:

字节码指令
00nop
02iconst_m1
12ldc
03iconst_0
B6invokevirtual
00nop
04iconst_1
B1return
14.2.5.1.7、异常表长度

长度:2字节
值:0x0000
对应位置:
在这里插入图片描述

14.2.5.1.7、异常表

由上面属性可知为空,此项无

14.2.5.1.8、Code的属性数量

长度:2字节
值:0x0001
对应位置:
在这里插入图片描述

14.2.5.1.9、Code的属性信息

通用为伪结构如下:

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}
14.2.5.1.9.1、属性1
14.2.5.1.9.1.1、属性名索引

长度:2字节
值:0x000A
对应位置:
在这里插入图片描述
对应javap展示结果:
在这里插入图片描述
查询官网可知,实际对应伪结构为:

LineNumberTable_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {   u2 start_pc;
        u2 line_number;	
    } line_number_table[line_number_table_length];
}
14.2.5.1.9.1.2、属性长度

长度:4字节
值:0x0000000A
对应位置:
在这里插入图片描述

14.2.5.1.9.1.3、行数表长度

长度:2字节
值:0x0002
对应位置:
在这里插入图片描述

14.2.5.1.9.1.3、行数表

之前伪结构中已经给出为:

 {   u2 start_pc;
        u2 line_number;	
    }
14.2.5.1.9.1.3.1、行数据1
项目开始位置行号
对应值0x00000x0003
十进制03

对应位置:
在这里插入图片描述
对应javap展示结果:
在这里插入图片描述

14.2.5.1.9.1.3.2、行数据2
项目开始位置行号
对应值0x00080x0004
十进制84

对应位置:
在这里插入图片描述

对应java展示结果位置:
在这里插入图片描述

15、属性计数器

长度:2字节
值:0x0001
对应位置:
在这里插入图片描述

16、属性信息表

伪结构如下:

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

16.1、属性信息1

16.1.1、属性名索引

长度:2字节
值:0x000D
对应位置:
在这里插入图片描述
对应javap展示结果:
在这里插入图片描述
则该属性实际的伪结构为:

SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;
}
16.1.2、属性长度

长度:4字节
值:0x00000002
对应位置:
在这里插入图片描述

16.1.3、源文件索引

长度:2字节
值:0x000E
对应位置:
在这里插入图片描述
对应javap展示结果:
在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值