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:xxinfo[]: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源代码自己进行编译按照该思路进行分析,相信你们一定也会有所收获!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Unity+OpenAI TTS] 集成openAI官方提供的语音合成服务,构建海王暖男数字人

1.简述 最近openAI官方发布了很多新功能&#xff0c;其中就包括了最新发布的TTS语音合成服务的api接口。说到这个语音合成接口&#xff0c;大家可能会比较陌生&#xff0c;但是说到chatgpt官方应用上的聊天机器人&#xff0c;那个台湾腔的海王暖男的声音&#xff0c;可能就有印…

Oracle与Redis Enterprise协同,作为企业缓存解决方案

来源&#xff1a;虹科云科技 虹科干货丨Oracle与Redis Enterprise协同&#xff0c;作为企业缓存解决方案 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 单独使用Oracle作为企业缓存数据库时&#xff0c;会出现哪些问题呢&#xff1f;使用Redis Enterprise与Oracle共…

汇编-CALL和RET指令

CALL指令调用一个过程&#xff0c; 使处理器从新的内存位置开始执行。过程使用RET(从过程返回) 指令将处理器转回到该过程被调用的程序点上。 CALL指令的动作&#xff1a; 1.将CALL指令的下一条指令地址压栈(作为子过程返回的地址) 2.将被调过程的地址复制到指令指针寄存器E…

七、通过libfdk_aac编解码器实现aac音频和pcm的编解码

前言 测试环境&#xff1a; ffmpeg的4.3.2自行编译版本windows环境qt5.12 AAC编码是MP3格式的后继产品&#xff0c;通常在相同的比特率下可以获得比MP3更高的声音质量&#xff0c;是iPhone、iPod、iPad、iTunes的标准音频格式。 AAC相较于MP3的改进包含&#xff1a; 更多的采…

Eclipse常用设置-乱码

在用Eclipse进行Java代码开发时&#xff0c;经常会遇到一些问题&#xff0c;记录下来&#xff0c;方便查看。 一、properties文件乱码 常用的配置文件properties里中文的乱码&#xff0c;不利于识别。 处理流程&#xff1a;Window -> Preferences -> General -> Ja…

「快学Docker」监控和日志记录容器的健康和性能

「快学Docker」监控和日志记录容器的健康和性能 1. 容器健康状态监控2. 性能监控3. 日志记录几种采集架构图 4. 监控工具和平台cAdvisor&#xff08;Container Advisor&#xff09;PrometheusGrafana 5. 自动化运维 1. 容器健康状态监控 方法1&#xff1a;需要实时监测容器的运…

机器人算法—ROS TF坐标变换

1.TF基本概念 &#xff08;1&#xff09;什么是TF&#xff1f; TF是Transformations Frames的缩写。在ROS中&#xff0c;是一个工具包&#xff0c;提供了坐标转换等方面的功能。 tf工具包&#xff0c;底层实现采用的是一种树状数据结构&#xff0c;根据时间缓冲并维护多个参考…

2022年03月 Scratch(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 红框中加入哪个选项积木,不能阻止气球下落? A: B: C: D:

实现极坐标图表QPolarChart的角度轴范围是[0,360]时,0度在水平右侧

目录 参考角度轴范围是[0,360]时&#xff0c;0度在水平右侧.h.cpp 参考 Qt数据可视化(QPolarChart雷达图) 默认QPolarChart的范围是[0,360]时&#xff0c;0度在垂直上方 如官方例子QValueAxis角度轴范围是[-100,100] 角度轴范围是[0,360]时&#xff0c;0度在水平右侧 原理&am…

@Scheduled注解 定时任务讲解

用于在Java Spring框架中定时执行特定任务的注解 Scheduled&#xff0c;它能够指定方法在特定时间间隔或特定时间点执行。默认参数是cron&#xff0c;cron参数被用来定义一个Cron表达式&#xff0c;它代表了任务执行的时间规则 参数如下 Cron 这是是一种时间表达式&#xff…

模拟量采集----测量输入的电流

生活中的模拟量有很多 大多都为电压信号和电流信号 今天讲如何测量输入的电流信号 通过欧姆定律可知 电流测量的测量&#xff1a;是将电流加载在固定阻值的电阻上&#xff0c;来测量这个电阻二端的电压 最后反算出电流的大小 所用的公式是IU/R 我们使用仿真软件来看测量…

CST同轴馈电步骤

CST同轴馈电步骤 算例1. 同轴内芯2. 填充材料3. 外皮4. GND减去一个圆形&#xff0c;使EMWAVE可以通过5. 添加端口6. 结果比较 算例 cst模型库中的一个圆贴片 1. 同轴内芯 2. 填充材料 他这里直接使用和介质基板一样的材料并且进行了合并&#xff0c;我就懒得再改了&#x…