JVM 之 class文件详解

目录

一. 前言

二. class文件结构

2.1. 文件格式

2.2. 魔数与版本号

2.3. 常量池

2.4. 访问标志

2.5. 类索引、父类索引和接口索引集合

2.6. 字段表集合

2.7. 方法表集合

2.8. 属性表集合

2.8.1. Code 属性表

2.8.2. Exceptions 属性

2.8.3. LineNumberTable 属性

2.8.4. LocalVariableTable 属性

2.8.5. SourceFile 属性

2.8.6. ConstantValue 属性

2.8.7. InnerClass 属性

2.8.8. Deprecated 属性和 Synthetic 属性

2.8.9. StackMapTable 属性

2.8.10. Signature 属性

2.8.11. BootstrapMethods 属性

三. 实例分析

3.1. 示例源码

3.2. 魔数

3.2. 版本号

3.3. 常量池

3.3.1. 常量池第 1 个常量

3.3.2. 常量池第 2 个常量

3.3.3. 常量池中第 3~4 常量

3.3.4. 常量池中第 5~14 及 17、18常量

3.3.5. 常量池第 15、16 个常量

3.4. 访问标志

3.5. 类索引、父类索引

3.6. 字段表集合

3.7. 方法表集合

3.7.1. 第 1 个方法表

3.7.2. 第 2 个方法表

3.8. Code 属性表与 LineNumberTable 属性表

3.9. SourceFile 属性


一. 前言

    我们平时在DOS界面中往往需要先运行 javac 命令,这个命令的直接结果就是产生相应的class文件,然后基于这个class文件才可以真正运行程序得到结果。当然,这是Java虚拟机的功劳,那么是不是Java虚拟机只能编译 .java 的源文件呢?答案是否定的。时至今日,Java虚拟机已经实现了语言无关性的特点。而实现语言无关性的基础是虚拟机和字节码的存储格式,Java虚拟机已经不和包括Java语言在内的任何语言绑定。它只与“class”文件这种特定的二进制文件相关联。在class文件中包含了Java虚拟机指令集和符号表以及若干辅助信息。可以很容易想到Java(本质上不是Java语言本身的平台无关性,而是其底层的Java虚拟机的平台无关性使然。)的跨平台,因为任何一门功能性语言都可以表示为能被Java虚拟机接受的有效的class文件。比如,除了Java虚拟机可以将Java源文件直接编译为class文件外,使用JRuby等其他语言的编译器一样可以把程序代码编译成class文件,由此可见,Java虚拟机并不关心class文件是由何种语言编译来的。

二. class文件结构

下面的图是一字排开形式,都是平级关系,由于图片排开太长,所以分行显示,如下面这样:

class文件格式采用一种类似C语言结构体的伪结构来存储数据,这种伪结构只有两种数据结构,即无符号数和表,解析class文件全是以这两个数据结构为基础。
无符号数:属于基本的数据类型,由1字节、2字节、4字节、8字节分别用u1、u2、u3、u8表示,可以用来描述数字、索引引用、数量值或者UTF-8编码构成字符串值。
表:是由多个无符号数或者表构成的复合数据结构,习惯以“_info”结尾class文件本质也可以看作一张表。

2.1. 文件格式

class文件格式严格按照下表的方式进行排列构成:

类型名称含义数量
u4magic魔数1
u2minor_version副版本号1
u2major_version主版本号1
u2constant_pool_count常量池计数器1
cp_infoconstant_pool常量池数据区(常量池表)constant_pool_count -1
u2access_flags访问标志(类的访问控制权限)1
u2this_class类索引1
u2super_class父类索引1
u2interfaces_count接口计数器1
u2interfaces接口信息数据区(接口表)interfaces_count
u2fields_count字段计数器1
field_infofields字段信息数据区(字段表)fields_count
u2methods_count方法计数器1
method_infomethods方法信息数据区(方法表)methods_count
u2attributes_count附加属性计数器1
attribute_infoattributes附加属性信息数据区(附加属性表)attributes_count

2.2. 魔数与版本号

    魔数固定为 0xCAFEBABE,4个字节。魔数的唯一作用在于确定这个class文件是否是Java虚拟机接受的class文件。如gif和jpeg等在文件头中都存在魔术,使用魔术而不是使用扩展名是基于安全性考虑的——扩展名可以随意被改变。

    紧接着魔数的4个字节是class文件版本号:版本号又分为副版本号主版本号。其中前两个字节用于表示副版本号,后两个字节用于表示主版本号。版本号是随着JDK版本的不同而表示不同的版本范围的。如果class文件的版本号超过虚拟机版本,将被拒绝执行。

2.3. 常量池

    在版本号之后,则是常量池入口,常量池在class文件中的作用非常重要,就是class文件中的资源仓库,通常也是占用class文件空间最大的数据项目之一。由于常量池长度不是一定的,所以在常量池的入口处放置了一个两个字节的常量池计数器来记录常量池的容量有多大。

常量池计数器:记录的是常量池数据区中的常量池项 cp_info 的数量。
1. 从1开始计数,第一个有用常量池项为1,所以常量池项的索引也是从1开始;
2. 至于索引为0的数据空间,class文件规范是如此定义的。

在常量池中主要存放字面量符号引用。字面量比较接近Java语言层面的常量概念,比如文本字符串、声明为final的常量值等。符号引用则主要包括三类常量:
1. 类和接口的全限定名;
2. 字段的名称和描述符;
3. 方法的名称和描述符。

// 常量池项 (cp_info) 的结构

cp_info{
    tag:xx
    info[]:xx
}

tag:类型,用标记下面info数组的数据类型。
info[]:若干个字节构成数组。

常量常量说明项目类型描述
CONSTANT_Utf8_info

表示字符串常量的值。

(字面量型结构体)

tag标志u1值为1
lengthu2UTF-8编码的字符串占用的字节数
bytesu1长度为length的UTF-8编码的字符串
CONSTANT_Integer_info表示4字节(int)的数值常量。

(字面量型结构体)

tag标志u1值为3
bytesu4按照高位在前存储的int值
CONSTANT_Float_info表示4字节(float)的数值常量。

(字面量型结构体)

tag标志u1值为4
bytesu4按照高位在前存储的float值
CONSTANT_Long_info表示8字节(long)的数值常量。

(字面量型结构体)

tag标志u1值为5
bytesu8按照高位在前存储的long值
CONSTANT_Double_info表示8字节(double)的数值常量。

(字面量型结构体)

tag标志u1值为6
bytesu8按照高位在前存储的double值
CONSTANT_Class_info

表示类或接口的完全限定名。

(引用型结构体)

tag标志u1值为7
indexu2指向全限定名常量项的索引
CONSTANT_String_info表示java.lang.String类型的常量对象。

(引用型结构体)

tag标志u1值为8
indexu2指向字符串字面量的索引
CONSTANT_Fieldref_info

表示类中的字段。

(引用型结构体)

tag标志u1值为9
indexu2指向声明字段的类或接口描述符CONSTANT_Class_info的索引项
indexu2指向字段描述符CONSTANT_NameAndType的索引项
CONSTANT_Methodref_info

表示类中的方法。

(引用型结构体)

tag标志u1值为10
indexu2指向声明方法的类描述符CONSTANT_Class_info的索引项
indexu2指向名称及类型描述符CONSTANT_NameAndType的索引项

CONSTANT_InterfaceMethodref

_info

表示类中的方法。

(引用型结构体)

tag标志u1值为11
indexu2指向声明方法的接口描述符CONSTANT_Class_info的索引项
indexu2指向名称及类型描述符CONSTANT_NameAndType的索引项

CONSTANT_NameAndType_info

表示字段或方法的名称和类型。

(引用型结构体)

tag标志u1值为12
indexu2指向该字段或方法名称常量项的索引
indexu2指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_info

表示方法句柄。

(引用型结构体)

tag标志u1值为15
CONSTANT_MethodType_info

表示方法类型。

(引用型结构体)

tag标志u1值为16
CONSTANT_Dynamic_info

表示一个动态计算常量。

(引用型结构体)

tag标志u1值为17

CONSTANT_InvokeDynamic

_info

用于表示invokedynamic指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型tag标志u1值为18
CONSTANT_Module_info表示一个模块tag标志u1值为19
CONSTANT_Package_info表示一个模块中开放或者导出的包tag标志u1值为20

细化了的常量池的结构会是类似下图所示的样子:

在JVM规范中,每个字段或者变量都有描述信息,描述信息的主要作用是 数据类型、方法参数列表、返回值类型等。基本参数类型和void类型都是用一个大写的字符来表示,对象类型是通过一个大写L加全类名表示,这么做的好处就是在保证JVM能读懂class文件的情况下尽量的压缩class文件体积。

基本数据类型表示:

字符类型字符类型
BbyteCchar
DdoubleFfloat
IintJlong
SshortZboolean
Vvoid

对象类型:前面加一个L,然后把.改为/,String------>Ljava/lang/String;(后面有一个分号)。
数组类型:每一个维度都是用一个前置 [ 来表示,比如: int[] ------>[ I,String [][]------>[[Ljava.lang.String;(后面有一个分号)。
用描述符来描述方法:先参数列表,后返回值的格式,参数列表按照严格的顺序放在()中
比如源码 String getUserByIdAndName(int id, String name) 的方法描述符号为(I,Ljava/lang/String;)Ljava/lang/String;(后面有一个分号)。

2.4. 访问标志

    常量池之后的数据结构是访问标志(access_flags),这个标志主要用于识别一些类或者接口层次的访问信息,主要包括:这个Class是类还是接口、是否定义public、是否定义abstract类型;如果是类的话是否被声明为final等。

类的标志访问如下:

标志名称标志值含义
ACC_PUBLIC0x0001是否为public
ACC_FINAL0x0010是否被声明为final,只有类能设置
ACC_SUPER0x0020是否允许使用invokespecial字节码指令的新语义
ACC_INTERFACE0x0200标识这是一个接口
ACC_ABSTRACT0x0400是否为abstract类型,对于接口或者抽象类来说,此标志位为真,其他类型为假
ACC_SYNTHETIC0x1000是否为这个类是由编译器自动产生,并非由用户代码产生的
ACC_ANNOTATION0x2000标识这是一个注解
ACC_ENUM0x4000标识这是一个枚举类
ACC_MODULE0x8000标识这是一个模块

字段的标志访问如下:

方法的标志访问如下: 

两个访问修饰符直接通过位运算来实现的。例如:现在有一个class文件访问标识为00 21。那么实际它代表的是0x0021 = 0x0020 位运算 0x0001,即它代表这个class的访问权限是ACC_PUBLIC和ACC_SUPER。

2.5. 类索引、父类索引和接口索引集合

    类索引和父类索引都是一个两个字节的数据,而接口索引是一组两字节的数据集合,class文件中由这三项数据来确定该类型的继承关系,类索引用于确定该类的全限定类名,父类索引用于确定该类父类的全限定类名。由于Java语言特性不支持多类继承,所以父类索引只有一个,接口索引集合用于描述这类实现了哪些接口。

2.6. 字段表集合

    字段表用于描述接口或者类中声明的变量。字段包括类级变量实例级变量,但是不包括方法内部声明的局部变量(这些变量是存储在Java虚拟机栈中的局部变量表中的)。自然,描述一个字段的信息包括:字段的作用域(public、protected、private)、实例变量与否(static)、可变性(final)、并发可见性(volatile)、可否被序列化(transient)、字段数据类型(基本数据类型、对象、数组)、字段名称。

字段的信息也被存放在一张表中,其字段表包括三种类型:
1. u2类型访问标志(access_flags),其访问标志在access_flags中。
2. u2类型的name_index(字段的简单名称)。
3. u2类型的描述符(descriptor_index)。

字段表格式:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

2.7. 方法表集合

    JVM中堆方法表的描述与字段表是一致的,包括了:访问标志、名称索引、描述符索引、属性表集合。方法表的结构与字段表是一致的,区别在于访问标志的不同。在方法中不能用volatile和transient关键字修饰,所以这两个标志不能用在方法表中。在方法中添加了字段不能使用的访问标志,比如方法可以使用synchronized、native、strictfp、abstract关键字修饰,所以在方法表中就增加了相应的访问标志。

注意:如果父类方法没有在子类中重写,那么在方法中不会自动出现来自父类的方法信息。同样的,有可能添加编译器自动增加的方法,比如方法。

方法表的结构如下:

2.8. 属性表集合

    前面的class文件、字段表和方法表都可以携带自己的属性信息,这个信息用属性表进行描述,用于描述某些场景专有的信息。在属性表中没有类似class文件的数据项目类型和顺序的严格要求,只要新的属性不与现有的属性名重复,任何人都可以向属性表中写入自己定义的属性信息。 

2.8.1. Code 属性表

    Java程序方法体中的代码经过 javac 编译最终编译成的字节码指令就保存在Code属性中。但是并非所有的方法表都必须存在这个属性。Code属性是class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码(Code)和元数据(Metadata,包括类、字段、方法定义及其其他信息)两部分,那么在整个class文件中,Code属性用于描述代码,所有其他的数据项目都用于描述元数据。

Code属性表的结构:

类型名称数量说明
u2attribute_name_index1指向CONSTANT_Utf8_info型常量的索引,此常量固定为“Code”,代表该属性的属性名称
u4attribute_length1表示属性长度
u2max_stack1表示操作数栈深度的最大值
u2max_locals1表示局部变量表所需的存储空间,单位是变量槽(虚拟机为局部变量分配内存所使用的最小单位)
u1code_length1表示字节码长度
u2codecode_length用于存储字节码指令。用来存储java源文件编译后生成的字节码指令
u2exception_table_length1表示异常处理表集合长度
exception_infoexception_tableexception_table_length表示异常处理表集合,异常处理表并不是Code必须部分!
u2attributes_count1
attribute_infobattributesattribute_count

2.8.2. Exceptions 属性

    Exceptions 属性的作用是列举出方法中可能抛出的受检异常(Checked Exception),也就是描述throws 后的列举的异常。

2.8.3. LineNumberTable 属性

    LineNumberTable属性用于描述Java源码行号与上传字节码行号之间的对应关系,并不是运行的必须属性,但会默认生成到class文件中,主要作用就是抛出异常时会显示行号,以及调试程序时可以根据源码行号进行设置断点。

LineNumberTable的属性结构如下:

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2line_number_table_length1
line_number_infoline_number_tableline_number_table_length

line_number_table是一个 l类型为line_number_info的集合,包含start_pc和line_number两个属性都是占两个字节,前者是字节码行号,后者是java源码行号。 

2.8.4. LocalVariableTable 属性

    用于描述栈帧中局部变量表中的变量与Java源码中定义的变量的之间的关系。也不是必须的属性。如果没有这个属性,产生的直接影响就是当别人引用这个方法的时候,所有的参数名称都会丢失,IDE将会使用诸如 args0、args1 之类的参数进行显示。自然,当调试程序的时候,显示的参数名称是不可知的。

2.8.5. SourceFile 属性

    用于记录这个class文件的源码文件名称,当类名和文件名不一致时抛出异常。如果不使用这个属性,那么当抛出异常的时候,堆栈中将不会显示出错代码所属的文件名。

SourceFile属性的结构为:

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2sourcefile_index1

2.8.6. ConstantValue 属性

    ConstantValue 属性作用是通知虚拟机自动为静态变量赋值。要注意的是,只有被 static 关键字修饰的变量才可以使用这个属性(类变量)。对于非类变量,初始化是在方法中进行的;对于类变量可以选择两种方式进行变量的初始化:一是在类构造器方法中使用;二是是ConstantValue属性。目前Sun HotSpot的选择原则是:如果一个变量同时使用static和final关键字修饰,并且这个变量是基本数据类型或者 java.lang.String 类型的话,就使用ConstantValue属性进行初始化。如果没有被 final 修饰或者并非是基本数据类型,那么将会选择使用方法进行初始化。

2.8.7. InnerClass 属性

这个属性主要用于记录内部类与宿主类之间的关联关系。

2.8.8. Deprecated 属性和 Synthetic 属性

这两个属性都属于标志类型的布尔属性,只存在有没有的区别。

Deprecated 属性用于表示某个类、字段或者方法,已经被程序作者定为不再推荐使用,可以通过注解 @deprecated 实现。

Synthetic 属性代表此字段并不是由Java源码产生的,而是通过编译器自行添加的。

2.8.9. StackMapTable 属性

该属性的目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。

2.8.10. Signature 属性

    这个属性是专门用来记录泛型类型的,因为在Java语言采用的是擦除法实现的泛型,在字节码(Code属性)中,泛型信息编译之后会被擦除。擦除法的优点是能够节省泛型所占的内存空间,缺点是在运行期间无法通过反射得到泛型信息,而Signature属性则弥补了这一缺陷。现在的Java反射API已经能够得到泛型信息,功劳就在于这个属性。

2.8.11. BootstrapMethods 属性

    这个属性用于保存 invokedynamic 指令引用的引导方法限定符。该指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。

三. 实例分析

3.1. 示例源码

示例代码:

public class Test {
    private int m;

    public int inc(){
        return m + 1;
    }
}

编译后的 class文件(使用16进制的方式来打开):

cafe babe 0000 0034 0013 0a00 0400 0f09
0003 0010 0700 1107 0012 0100 016d 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 0369 6e63
0100 0328 2949 0100 0a53 6f75 7263 6546
696c 6501 0009 5465 7374 2e6a 6176 610c
0007 0008 0c00 0500 0601 0004 5465 7374
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7400 2100 0300 0400 0000 0100 0200
0500 0600 0000 0200 0100 0700 0800 0100
0900 0000 1d00 0100 0100 0000 052a b700
01b1 0000 0001 000a 0000 0006 0001 0000
0001 0001 000b 000c 0001 0009 0000 001f
0002 0001 0000 0007 2ab4 0002 0460 ac00
0000 0100 0a00 0000 0600 0100 0000 0400
0100 0d00 0000 0200 0e

3.2. 魔数

ca fe ba be // 魔数值0xCAFEBABE

上述代码可以看出 class文件的魔术值为0xCAFEBABE。

    MagicNumber 为每个class文件的头4个字节,唯一作用就是用于判断这个文件是否为一个能被虚拟机接受的 class文件。为什么使用魔数不使用扩展名进行判断?(出于安全考虑,扩展名可以随意修改,魔术值只要没有被广泛采用就不会引起混淆)

3.2. 版本号

00 00 00 34 // 前两位为次版本号,后两位为主版本号

    版本号为魔数后4个字节,前两个字节为副版本号,后两个字节为主版本号,0034十进制是52表示Java8,关于副版本号,在JDK1.2时短暂使用过,从JDK1.2到JDK12之前副版本号均为零,未被使用过。

3.3. 常量池

00 13       // 00 13表示常量中常量的数量为18 

3.3.1. 常量池第 1 个常量

观察例子中常量池第一个常量,它的标志位为0x0a,根据2.3 章节表可得知这个常量类型为CONSTANT_Methoddref_info,CONSTANT_Methoddref_info 类型结构为:

类型名称数量描述
u1tag1值为10
u2index1指向方法的类描述符CONSTANT_Class_info索引项
u2index1指向名称及类型描述符CONTANT_Name_AndType索引项

结合字节码分析,该类型一共占5个字节:
第一个占1个字节,用于区分索引类型;
第二个占2个字节,用于指向该类型中的CONSTANT_Class_info在常量池中的索引;
第三个占2个字节,用于指向该类型中的CONSTANT_NameAndType在常量池中的索引。

0a			// 0a表示第 1 个常量类型为CONSTANT_Methoddref_info 长度为5个字节(包含标志位)
00 04       // 表示声明方法的类的字段 CONSTANT_Class_info的索引项在常量池第4项常量 
00 0f       // 表示名称及类型字段 CONSTANT_NameAndType的索引项在常量池的第 15 项常量

3.3.2. 常量池第 2 个常量

例子中常量池第二个常量,它的标志位为0x09,根据2.3 章节表可得知常量类型CONSTANT_Fieldref_info 的类型结构为:

类型名称数量描述
u1tag1值为9
u2index1指向方法的类描述符CONSTANT_Class_info索引项
u2index1指向名称及类型描述符CONTANT_Name_AndType索引项

结合字节码分析,该类型一共占5个字节:
第一个占1个字节,用于区分索引类型;
第二个占2个字节,用于指向该类型中的CONSTANT_Class_info在常量池中的索引;
第三个占2个字节,用于指向该类型中的CONSTANT_NameAndType在常量池中的索引。

09			// 表示第 2 个常量类型为 CONSTANT_Fieldref_info 长度为5个字节(包含标志位)
00 03 		// 表示声明方法的类的字段 CONSTANT_Class_info 的索引项在常量池第 3 项常量 
00 10 		// 表示字段CONSTANT_Name-AndType的索引在常量池的第 16 项

3.3.3. 常量池中第 3~4 常量

例子中第3个和第4个常量类型一样,它的标志位都为0x07,根据2.3 章节表可得知常量类型为CONTANT_Class_info,类型结构为:

类型名称数量描述
u1tag1值为7
u2index1指向全限定名常量的索引

结合字节码分析,该类型一共占3个字节:
第一个占1个字节,用于区分索引类型;
第二个占2个字节,用于指向全限定名常量项的索引。

07			// 表示第 3 个常量类型为 CONTANT_Class_info 长度为 3 个字节(包含标志位)
00 11       // 表示全限定名存在常量池的第 17 项常量
07    		// 表示第 4 个常量类型为 CONTANT_Class_info 长度为 3 个字节(包含标志位)
00 12 		// 表示全限定名存在常量池的第 18 项常量

3.3.4. 常量池中第 5~14 及 17、18常量

第5到14以及17、18常量,它们标志位都是0x01,根据2.3 章节表可得知常量类型为CONTANT_Utf8_info, 类型结构为 CONTANT_Class_info,类型结构为:

类型名称数量描述
u1tag1值为1
u2length1UTF-8编码的字符串占用了字节数
u1byteslength长度为length的UTF-8编码的字符串

结合字节码分析:
第一个占1个字节,用于区分索引类型;
第二个占2个字节,用于描述该常量占用多少(length)字节数;
第三个占length个字节数,用于存储utf-8编码的字符串 。

01 			// 表示第 5 个常量类型为 CONTANT_Utf8_info 长度为 4 个字节(包含标志位)
00 01 		// 表示utf-8编码占用了字节数为 1
6d    		// 6d 十进制为109 utf-8对应 m
01			// 表示第 6 个常量类型为 CONTANT_Utf8_info
00 01 		// 表示长度为 1
49 			// 49 十进制为 73 utf8对应 I
01			// 第 7 个常量为CONTANT_Utf8_info
00 06		// 常量长度为 6
3c 69 6e 69 74 3e // 一一对应 '<init>'
<!-- 下面utf全部简略的描述 -->
01 			// 第 8 个常量
00 03 		// 长度为 3
28 29 56	// utf对应 ()V
01 			// 第 9 个常量
00 04 		// 长度为 4
43 6f 64 65 // Code
01 			// 第 10 个变量
00 0f		// 长度为 15
4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 // LineNumberTable
01 			// 第 11 个变量
00 03		// 长度为3
69 6e 63	// inc
01			// 第 12 个变量
00 03		// 长度为3
28 29 49	// ()I
01  		// 第 13 个变量
00 0a		// 长度为10
53 6f 75 72 63 65 46 69 6c 65  // SourceFile
01 			// 第 14 个变量
00 09 		// 长度为9
54 65 73 74 2e 6a 61 76 61 // Test.java
······
01 			// 第 17 个常量为 utf-8
00 04		// 长度为 4
54 65 73 74 // Test
01			// 第 18 个常量为 utf-8
00 10		// 长度为 16
6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74  // java/lang/Object

3.3.5. 常量池第 15、16 个常量

第15、16个常量,它们的标志为都是0x0c,根据2.3 章节表可得知常量类型为CONSTANT_NameAndType_info,类型结构如下:

类型名称数量描述
u1tag1值为12
u2index1指向该字段或方法名称常量项索引
u2index1指向指向该字段或方法描述常量项索引

结合字节码分析,该类型一共占5个字节:
第一个占1个字节,用于区分索引类型;
第二个占2个字节,指向该字段或方法名称常量项索引;
第三个占2个字节,指向指向该字段或方法描述常量项索引。

0c 			// 表示第 15 个常量量类型为 CONSTANT_NameAndType_info 长度为5个字节(包含标志位)
00 07 		// 表示该字段的表示的字段或方法的名称为常量池的第7个常量
00 08 		// 表示该字段表示的字段或方法的描述为位常量池的第8个常量
0c			// 表示第 16 个常量量类型为 CONSTANT_NameAndType_info 长度为5个字节(包含标志位)
00 05		// 表示该字段的表示的字段或方法的名称为常量池的第5个常量
00 06		// 表示该字段表示的字段或方法的描述为位常量池的第6个常量

到这常量池已经全部解析完成: 可以使用 javap -verbose 命令输出常量表,对常量表进行验证:

3.4. 访问标志

0021 根据2.3 章节表可得知该类使用 invokespecial 字节码指令的新语义,且被public修饰。

00 21		// access_flags 标志位占用 2个字节位,0021表示被public修饰符修饰,且使用invokespecial字节码指令的新语义

3.5. 类索引、父类索引

00 03 		// this_class 类索引,占用 2 个字节,表示在类名称存放在常量池第 3 项常量中
00 04 		// super_class 父类索引,占用 2 个字节,表示父类名称存放在常量池第 4 项常量中
00 			// interface_count 占用 1 个字节,表示实现接口的数量为0
00			// interfaces 接口为空

3.6. 字段表集合

<!-- 字段表集合 -->
00 01 		// fields_count 表示只有 1 个字段表数据 占用 2 个字节
00 02		// access_flags 表示字段修饰符为private 占用 2 个字节
00 05 		// name_index 表示字段名称存放在常量池第 5 项常量 占用 2 个字节
00 06		// descriptor_index 表示字段方法的描述存放在常量池的第 6 项常量 占用 2 个字节
00 			// attributes_count 属性表数量为0 占用 2 个字节
00 			// attributes 表示属性表集合为空

3.7. 方法表集合

<!-- 方法表集合 -->
00 02 		// methods_count 表示表示方法表集合中包含 2 个方法

3.7.1. 第 1 个方法表

<!-- 第一个方法表 -->
00 01 		// access_flags 表示方法修饰符为public 占用 2 个字节
00 07		// name_index 方法名称存放在常量池第 7 个 占用 2 个字节
00 08		// descriptor_index 表示方法描述存放在常量池第 8 个 占用 2 个字节
00 01		// attributes_count 表示此方法属性表集合中有 1 项属性 占用 2 个字节
<!-- attribute_info (code)-->
00 09		// attribute_name_index 表示属性存放在常量池第 9 项 占用 2 个字节
00 00 00 1d // attribute_lenth 表示该属性的长度 占用 4 个字节
00 01  		// max_stack 操作数栈的最大深度 占用 2 个字节
00 01 		// max_locals 局部变量表所需的存储空间 占用两个字节
00 00 00 05 // code_length 表示字节码长度,占用 4 个字节
2a b7 00 01 b1 // code 用于存储编译后的字节码指令 占用code_length 个字节

00 00 		// exception_table_length 异常表长度 占用 2 个字节

00 01 		// code属性中的 attributes_count 占用两个字节
00 0a 		// attribute_name_index 属性名称在常量池的第10项常量
00 00 00 06 // LineNumber类型的 attribute_length 属性长度 占用 4 个字节
00 01 		// line_number_table_length 表示有几个line_number_info类型的数据 占用 2 个字节
<!-- line_number_info -->
00 00 		// start_pc 字节码行号
00 01 		// line_number java源码行号

3.7.2. 第 2 个方法表

<!-- 第二个方法表 -->
00 01 		// access_flags 表示方法修饰符为public 占用 2 个字节
00 0b 		// name_index 方法名称存放在常量池第 11 个 占用 2 个字节
00 0c 		// descriptor_index 表示方法描述存放在常量池第 12 个 占用 2 个字节
00 01 		// attributes_count 表示此方法属性表集合中有 1 项属性 占用 2 个字节
<!-- attribute_info (code) -->
00 09 		// attribute_name_index 表示属性存放在常量池第 9 项 占用 2 个字节
00 00 00 1f // attribute_lenth 表示该属性的长度 占用 4 个字节
00 02		// max_stack 操作数栈的最大深度 占用 2 个字节
00 01 		// max_locals 局部变量表所需的存储空间 占用 2 个字节
00 00 00 07 // code_length 表示字节码的长度 占用 4 个字节
2a b4 00 02 04 60 ac// code 用于存储编译后的字节码指令 占用code_length个字节
00 00 		// exception_table_length 异常表长度 占用 2 个字节
00 01 		// cod属性中的 attributes_count 表示有几个属性 占用两个字节
00 0a 		// attribute_name_index 属性名在常量池中的索引 表示是常量池中第 10 个索引 占用 2 个字节
00 00 00 06 // LineNumber类型的 attribute_length 属性长度占用 4 个字节
00 01 		// line_number_table_length 表示有几个 line_number_info类型的数据 站哟 两个字节
<!-- line_number_info -->
00 00 		// start_pc 字节码行号
00 04 		// line_number java源码行号

3.8. Code 属性表与 LineNumberTable 属性表

<!-- 第一个方法 attribute_info (code)-->
00 09		// attribute_name_index 表示属性存放在常量池第 9 项 占用 2 个字节
00 00 00 1d // attribute_lenth 表示该属性的长度 占用 4 个字节
00 01  		// max_stack 操作数栈的最大深度 占用 2 个字节
00 01 		// max_locals 局部变量表所需的存储空间 占用两个字节
00 00 00 05 // code_length 表示字节码长度,占用 4 个字节
2a b7 00 01 b1 // code 用于存储编译后的字节码指令 占用code_length 个字节

00 00 		// exception_table_length 异常表长度 占用 2 个字节

00 01 		// code属性中的 attributes_count 占用两个字节
00 0a 		// attribute_name_index 属性名称在常量池的第10项常量
00 00 00 06 // LineNumber类型的 attribute_length 属性长度 占用 4 个字节
00 01 		// line_number_table_length 表示有几个line_number_info类型的数据 占用 2 个字节
<!-- line_number_info -->
00 00 		// start_pc 字节码行号
00 01 		// line_number java源码行号

<!--第二个方法 attribute_info (code) -->
00 09 		// attribute_name_index 表示属性存放在常量池第 9 项 占用 2 个字节
00 00 00 1f // attribute_lenth 表示该属性的长度 占用 4 个字节
00 02		// max_stack 操作数栈的最大深度 占用 2 个字节
00 01 		// max_locals 局部变量表所需的存储空间 占用 2 个字节
00 00 00 07 // code_length 表示字节码的长度 占用 4 个字节
2a b4 00 02 04 60 ac// code 用于存储编译后的字节码指令 占用code_length个字节
00 00 		// exception_table_length 异常表长度 占用 2 个字节
00 01 		// cod属性中的 attributes_count 表示有几个属性 占用两个字节
00 0a 		// attribute_name_index 属性名在常量池中的索引 表示是常量池中第 10 个索引 占用 2 个字节
00 00 00 06 // LineNumber类型的 attribute_length 属性长度占用 4 个字节
00 01 		// line_number_table_length 表示有几个 line_number_info类型的数据 站哟 两个字节
<!-- line_number_info -->
00 00 		// start_pc 字节码行号
00 04 		// line_number java源码行号

3.9. SourceFile 属性

<!-- 属性表 -->
00 01 		// attributes_count 表示有几个属性 占用两个字符
<!-- attribute_info -->
00 0d 		// attribute_name_index 属性名称在常量池中的索引 占用 2 个字节
00 00 00 02 // attribute_length 属性长度 占用 4 个字节
00 0e		// sourcefile_index 资源文件名称在常量池中的索引

到此为止,class文件全部解析完成,读者也可以使用该Java源代码自己进行编译按照该思路进行分析,相信你们一定也会有所收获!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/175566.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

哈希(c++)

目录 一、unordered系列关联式容器 &#xff08;一&#xff09;unordered_set &#xff08;二&#xff09;unordered_map 练习&#xff1a;961. 在长度2N的数组中找出重复N次的元素 二、哈希的底层结构 &#xff08;一&#xff09;哈希概念 &#xff08;二&#xff09;哈…

vue实现聊天栏定位到最底部(超简单、可直接复制使用)

原理 通过watch监听聊天内容的加载&#xff0c;一旦加载完成或者数据更新触发vue的数据监听时&#xff0c;就重新修改【滚动滑钮到滚动条顶部的距离滚动条的高度】&#xff0c;从而实现定位到底部的效果。 实现 1、布局 新建一个div&#xff08;聊天框&#xff0c;如下&…

【数据结构算法(二)】链表总结

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 普通单向链表 双向链表 带哨兵的链表 环形链表 ⭐双向带头带环链表的实现⭐ ⭐链表基础OJ⭐ 普通单向链表 结点结构&#xff1a;只有val 和 next指针 初始时&#xff1a;head null; 双向链表 指针&…

dedecms标签

【Arclist 标记】这个标记是DedeCms最常用的一个标记&#xff0c;也叫自由列表标记&#xff0c;其中 hotart、coolart、likeart、artlist、imglist、imginfolist、specart、autolist 这些标记都是由这个标记所定义的不同属性延伸出来的别名标记。功能说明&#xff1a;获取指定的…

suricata识别菜刀流量

一、捕获菜刀流量 payload特征&#xff1a; PHP: <?php eval($_POST[caidao]);?> ​ ASP: <%eval request(“caidao”)%> ​ ASP.NET: <% Page Language“Jscript”%><%eval(Request.Item[“caidao”],“unsafe”);%>数据包流量特征&#xff1a; …

【C++上层应用】3. 动态内存

文章目录 【 1. new和delete运算符 】1.1 new 分配内存1.2 delete 释放内存1.3 实例 【 2. 数组的动态内存分配 】2.1 一维数组2.2 二维数组2.3 三维数组 【 3. 对象的动态内存分配 】 C 程序中的内存分为两个部分&#xff1a; 栈&#xff1a;在 函数内部 声明的所有变量都将占…

LeetCode算法心得——使用最小花费爬楼梯(记忆化搜索+dp)

大家好&#xff0c;我是晴天学长&#xff0c;很重要的思想动规思想&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1&#xff09;使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从…

WinApp自动化测试之工具的选择

WinApp&#xff08;Windows APP&#xff09;是运行在Windows操作系统上的应用程序&#xff0c;通常会提供一个可视的界面&#xff0c;用于和用户交互。 例如运行在Windows系统上的Microsoft Office、PyCharm、Visual Studio Code、Chrome&#xff0c;都属于WinApp。常见的WinA…

【Linux】xfs文件系统的xfs_info命令

xfs_info命令 ① 查看命令工具自身的版本号 xfs_info -V ② 查看指定XFS设备的详细信息 xfs_info <device_name> 其他的一些命令可以使用man xfs_info去查阅man手册&#xff1a;

vue3中v-for报错 ‘item‘ is of type ‘unknown‘

报错 在写vue3ts的项目&#xff0c;得到一个数组&#xff0c;需要循环展示&#xff0c;使用v-for循环&#xff0c;写完之后发现有个报错&#xff0c;如下&#xff1a; 解决 props的时候使用PropType将数据类型完整标注即可 以为没有显示的表示出list中item的类型&#xff…

面对网络渠道低价 品牌如何应对

品牌在发展过程中&#xff0c;会不断拓展自己的销售渠道&#xff0c;网站渠道是顺应消费者习惯的一种销售战场&#xff0c;没有品牌会轻易丢弃这个渠道&#xff0c;但是网络渠道的低价又是很常见的&#xff0c;所以只有及时的治理渠道低价&#xff0c;对应的渠道才会发展越来越…

python数据结构与算法-10_递归

递归 Recursion is a process for solving problems by subdividing a larger problem into smaller cases of the problem itself and then solving the smaller, more trivial parts. 递归是计算机科学里出现非常多的一个概念&#xff0c;有时候用递归解决问题看起来非常简单…

呼叫中心自建好还是云外呼好用?

传统的呼叫中心在科技的发展下已经被不适用了&#xff0c;都开始使用起智能化的呼叫中心&#xff0c;一个是自建式呼叫中心&#xff0c;一个是云外呼系统。那自建式呼叫中心与云外呼系统的区别有哪些呢&#xff1f; 1、企业自建呼叫中心 劣势 系统维护更新难&#xff1a;自建…

Proxifier联动BurpSuite抓取小程序

直接上软件包 Proxifier安装包https://pan.quark.cn/s/7fb9ad6deb7cProxifier配置文件https://pan.quark.cn/s/049c5f21c97e 无话可说直接操作 1、安装Proxifier步骤可以省略..... 2、将下面文件导入到Proxifier中 3、左上角文件-导入配置文件&#xff08;因为我已经导入过…

快来瞧瞧这样制作出来的电子画册,还便于分享宣传呢!

说起电子画册制作&#xff0c;很多人都不知道从何入手。与传统纸质画册相比&#xff0c;电子画册最大的优点是便于传阅&#xff0c;通过微信、QQ等社交平台都能进行转发和分享。而且内容的排版基本上和纸质画册一致&#xff0c;不同的是&#xff0c;无论图片还是文字都可以赋予…

网络渗透测试(wireshark 抓取QQ图片)

1.打开wireshark 这里我用的wifi连接 所以点开wifi就好 打开wifi之后就开始在本机上进行抓包了 我们先给我们的QQ发送一张图片&#xff0c;用自己的手机发送给电脑 然后点击左上角的正方形&#xff0c;停止捕获抓包 QQ的关键词是oicq&#xff0c;所以我们直接找 打开oicq …

2023年国自然植物科学相关面上项目信息公布(小麦、大麦、棉花、大豆、玉米)

2024年申报国自然项目基金撰写及技巧http://mp.weixin.qq.com/s?__bizMzA4NTAwMTY1NA&mid2247575761&idx1&sn32dbacd3393f3b76a1e0668e4b8b3c89&chksm9fdd7c08a8aaf51ec31d4790067bb57751a09947eeb7e728b8c008d26b89adba37e0cab32a62&scene21#wechat_redi…

【23真题】难!下沙“小清华”难度爆增!

今天分享的是23年“下沙小清华”杭州电子科技大学843的信号与系统试题及解析。 本套试卷难度分析&#xff1a;22年杭电843考研真题&#xff0c;我也发布过&#xff0c;若有需要&#xff0c;戳这里自取&#xff01;平均分为112分&#xff0c;最高分为145分&#xff01;该院校23…

requests 库中响应最大文件大小和最大连接超时时间的设定

最近&#xff0c;requests-toolbelt库的开发者jvanasco提出了一项特性请求&#xff0c;即在发送请求时设置响应的最大文件大小和最大连接超时时间。 对于最大连接超时时间的问题&#xff0c;我们可以借鉴requests-toolbelt库的开发者kevinburke的建议&#xff0c;将请求放入线程…

基于变色龙算法优化概率神经网络PNN的分类预测 - 附代码

基于变色龙算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于变色龙算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于变色龙优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…