上节我们分析了class类文件结构并分析了class文件常量池和17种不同的常量项,接下来再回到这个结构
goClassFile {
u4 magic; // 魔数,用于标识文件格式,固定为0xCAFEBABE。
u2 minor_version; // 类文件的次版本号,与major_version一起决定了该类文件的JVM版本要求。
u2 major_version; // 类文件的主版本号。比如52表示Java SE 8,53表示Java SE 9。
u2 constant_pool_count; // 常量池中的项数加1。实际项数是constant_pool_count - 1。
cp_info constant_pool[constant_pool_count - 1]; // 常量池数组,存储类文件中用到的各种常量,如类名、方法名、字符串、数字等。
u2 access_flags; // 类访问标志,用于标识类或接口的访问属性,如public、final、super、interface、abstract等。
u2 this_class; // 指向constant_pool中的索引,表示当前类的符号引用,通常是一个CONSTANT_Class_info项。
u2 super_class; // 指向constant_pool中的索引,表示当前类的父类的符号引用。如果当前类是Object类,没有父类,这个值为0。
u2 interfaces_count; // 实现的接口数。
u2 interfaces[interfaces_count]; // 实现接口的列表,每个接口都是一个对constant_pool中CONSTANT_Class_info项的索引。
u2 fields_count; // 字段数量,即类或接口中声明的字段数(不包括从父类继承的字段)。
field_info fields[fields_count]; // 字段信息的数组,每个field_info项表示一个字段的定义,如字段的名称、类型、修饰符、属性等。
u2 methods_count; // 方法数量,即类或接口中声明的方法数(不包括从父类继承的方法)。
method_info methods[methods_count]; // 方法信息的数组,每个method_info项表示一个方法的定义,如方法的名称、描述符、修饰符、属性(包括字节码)等。
u2 attributes_count; // 类的附加属性数量。
attribute_info attributes[attributes_count]; // 类文件级别的附加属性数组,比如"SourceFile"、"InnerClasses"、"EnclosingMethod"等。
}
这里面有三个字段和其他的不同
go...
field_info fields[fields_count]; // 字段信息的数组,每个field_info项表示一个字段的定义,如字段的名称、类型、修饰符、属性等。
...
method_info methods[methods_count]; // 方法信息的数组,每个method_info项表示一个方法的定义,如方法的名称、描述符、修饰符、属性(包括字节码)等。
...
attribute_info attributes[attributes_count]; // 类文件级别的附加属性数组,比如"SourceFile"、"InnerClasses"、"EnclosingMethod"等。
分别是方法表,字段表和属性表,显而易见,描述的分别是类的方法,字段和属性,Java虚拟机规范给出了对应的结构 首先是字段结构
goField_info {
u2 access_flags; // 字段的访问标志
u2 name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示字段名称
u2 descriptor_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示字段描述符
u2 attributes_count; // 字段属性的数量
attribute_info attributes[attributes_count]; // 字段属性表
}
access_flags:字段的访问标志,如 public、private、static 等。
name_index:指向常量池中 CONSTANT_Utf8_info 的索引,表示字段的名称。
descriptor_index:指向常量池中 CONSTANT_Utf8_info 的索引,表示字段的描述符(如 I 表示 int,Ljava/lang/String; 表示 String)。
attributes_count:字段属性的数量。
attributes:字段属性表,包含字段的其他属性信息,如 ConstantValue 属性(用于表示静态常量的初始值)。
我们可以看到字段也是有属性表的
接下来是方法结构
goMethod_info {
u2 access_flags; // 方法的访问标志
u2 name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示方法名称
u2 descriptor_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示方法描述符
u2 attributes_count; // 方法属性的数量
attribute_info attributes[attributes_count]; // 方法属性表
}
access_flags:方法的访问标志,如 public、private、static 等。
name_index:指向常量池中 CONSTANT_Utf8_info 的索引,表示方法的名称。
descriptor_index:指向常量池中 CONSTANT_Utf8_info 的索引,表示方法的描述符(如 (I)V 表示参数为 int,返回值为 void 的方法)。
attributes_count:方法属性的数量。
attributes:方法属性表,包含方法的其他属性信息,如 Code 属性(用于表示方法的字节码指令)、Exceptions 属性(用于表示方法声明抛出的异常)等。
方法也是有属性表的,接下来我们来看一下属性表中属性的结构是怎样的
goattribute_info {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u1 info[attribute_length]; // 属性的具体内容
}
attribute_name_index:指向常量池中 CONSTANT_Utf8_info 的索引,表示属性的名称。
attribute_length:属性的长度,不包括前6个字节。
info:属性的具体内容,根据属性的不同,内容也会有所不同。例如,Code 属性的内容包含方法的字节码指令、异常表等。
可以看到这个info是灵活的,所以每种属性都会有自己的结构,也就是属性内容,JVM预定义了一些属性,这些属性有描述class文件的也有描述方法和字段的,其中有只能class用的或者只有方法或者字段能用的属性,也有字段和方法类都能用的属性,接下来总结一下这些属性和他们的结构还有作用
用途:用于描述方法的字节码指令、异常表、局部变量表等信息。
结构:
javaCode_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
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; // 指向常量池中 CONSTANT_Class_info 的索引,表示捕获的异常类型
} exception_table[exception_table_length];
u2 attributes_count; // 附加属性的数量
attribute_info attributes[attributes_count]; // 附加属性表
}
code属性是接触的最多的属性了,面试的时候也会问异常表的事情,现在更清楚一点了,在方法信息里有个属性表,所有方法都有一个code属性,Code属性还有个异常表,包含了程序的异常处理信息,Code属性还有个Code字段,包含字节码序列
这里为什么还有一个附加属性表呢?Code本身就是一个属性,附加属性表是为了扩展Code属性的信息,比如有一个LineNumberTable:属性,这个附加属性表将字节码指令与源代码中的行号关联起来。当发生异常或使用调试器时,可以通过这个表找到出错的源代码行。还有其他的一些属性,如LocalVariableTable等也是用来扩展Code属性信息的属性
用途:用于表示静态常量的初始值。
结构:
javaConstantValue_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 constantvalue_index; // 指向常量池中常量值的索引
}
用途:用于表示方法声明抛出的异常。
结构:
javaExceptions_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 number_of_exceptions; // 声明抛出的异常数量
u2 exception_index_table[number_of_exceptions]; // 指向常量池中 CONSTANT_Class_info 的索引,表示异常类型
}
用途:用于描述内部类信息。
结构:
javaInnerClasses_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 number_of_classes; // 内部类数量
{ u2 inner_class_info_index; // 指向常量池中 CONSTANT_Class_info 的索引,表示内部类
u2 outer_class_info_index; // 指向常量池中 CONSTANT_Class_info 的索引,表示外部类
u2 inner_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示内部类名称
u2 inner_class_access_flags; // 内部类的访问标志
} classes[number_of_classes];
}
举个例子,假如有以下类
javapublic class OuterClass {
private String outerField = "Outer";
// 内部类
public class InnerClass {
public void innerMethod() {
System.out.println("Inner method, accessing: " + outerField);
}
}
}
当我们编译这个Java文件时,编译器会生成两个类文件:OuterClass.class 和 OuterClass$InnerClass.class。在OuterClass.class文件中,会有一个InnerClasses属性来描述这个内部类。
假设这个类文件的常量池中包含以下常量(这里只列出相关部分):
常量池索引 #1: CONSTANT_Class_info 对应 OuterClass
常量池索引 #2: CONSTANT_Class_info 对应 OuterClass$InnerClass
常量池索引 #3: CONSTANT_Utf8_info 对应 InnerClass
InnerClasses属性可能会看起来像这样:
goInnerClasses_attribute {
u2 attribute_name_index: #4 // 常量池索引 #4 指向常量池中的 "InnerClasses" 字符串
u4 attribute_length: 10 // 属性长度,不包括前6个字节
u2 number_of_classes: 1 // 表示有一个内部类
classes[1] {
u2 inner_class_info_index: #2 // 指向常量池索引 #2, 对应 `OuterClass$InnerClass`
u2 outer_class_info_index: #1 // 指向常量池索引 #1, 对应 `OuterClass`
u2 inner_name_index: #3 // 指向常量池索引 #3, 对应 `InnerClass` 名称
u2 inner_class_access_flags: 0x0001 // 表示 `public` 访问权限
}
}
用途:用于描述局部类或匿名类所在的 enclosing 方法。
结构:
javaEnclosingMethod_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 class_index; // 指向常量池中 CONSTANT_Class_info 的索引,表示 enclosing 类
u2 method_index; // 指向常量池中 CONSTANT_NameAndType_info 的索引,表示 enclosing 方法
}
这个我们也举个例子,假如有以下类
javapublic class OuterClass {
public void someMethod() {
class LocalClass {
void localMethod() {
System.out.println("This is a local class");
}
}
}
}
在这个例子中,LocalClass是在someMethod()方法内部定义的。编译器生成的LocalClass.class文件会包含一个EnclosingMethod属性。
假设常量池中包含以下相关常量:
常量池索引 #1: CONSTANT_Class_info 对应 OuterClass
常量池索引 #2: CONSTANT_NameAndType_info 对应 someMethod()V(表示someMethod方法的名称和描述符)
那么LocalClass.class文件中的EnclosingMethod属性可能如下:
goEnclosingMethod_attribute {
u2 attribute_name_index: #3 // 常量池索引 #3, 指向 "EnclosingMethod" 字符串
u4 attribute_length: 4 // 属性长度,固定为4
u2 class_index: #1 // 指向常量池索引 #1, 对应 `OuterClass`
u2 method_index: #2 // 指向常量池索引 #2, 对应 `someMethod()V`
}
用途:用于标记编译器生成的类成员(如桥方法)。
结构:
javaSynthetic_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
}
这个属性用来描述一些由编译器生成的类或者方法,如桥接方法,桥接方法是编译器为了完成某些操作生成的,如泛型的类型擦除,编译器会生成一个类型擦除后的方法
用途:用于表示泛型类型信息。
结构:
javaSignature_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 signature_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示签名
}
用途:用于表示源文件的名称。
结构:
javaSourceFile_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 sourcefile_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示源文件名称
}
用途:用于调试,表示字节码指令与源代码行号的对应关系。
结构:
javaLineNumberTable_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 line_number_table_length; // 行号表的长度
{ u2 start_pc; // 字节码偏移量
u2 line_number; // 源代码行号
} line_number_table[line_number_table_length];
}
这就是刚刚提到的Code属性的附加属性之一
用途:用于调试,表示局部变量表的信息。
结构:
javaLocalVariableTable_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 local_variable_table_length; // 局部变量表的长度
{ u2 start_pc; // 字节码偏移量
u2 length; // 局部变量的作用范围长度
u2 name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示局部变量名称
u2 descriptor_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示局部变量描述符
u2 index; // 局部变量在局部变量表中的索引
} local_variable_table[local_variable_table_length];
}
这也是Code的附加属性
用途:用于调试,表示局部变量表的泛型类型信息。
结构:
javaLocalVariableTypeTable_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 local_variable_type_table_length; // 局部变量类型表的长度
{ u2 start_pc; // 字节码偏移量
u2 length; // 局部变量的作用范围长度
u2 name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示局部变量名称
u2 signature_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示局部变量签名
u2 index; // 局部变量在局部变量表中的索引
} local_variable_type_table[local_variable_type_table_length];
}
用途:用于标记类、字段或方法已过时。
结构:
javaDeprecated_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
}
用途:用于表示运行时可见的注解。
结构:
javaRuntimeVisibleAnnotations_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 num_annotations; // 注解数量
annotation annotations[num_annotations]; // 注解表
}
用途:用于表示运行时不可见的注解。
结构:
javaRuntimeInvisibleAnnotations_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 num_annotations; // 注解数量
annotation annotations[num_annotations]; // 注解表
}
用途:用于表示方法参数的运行时可见注解。
结构:
javaRuntimeVisibleParameterAnnotations_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u1 num_parameters; // 参数数量
{ u2 num_annotations; // 每个参数的注解数量
annotation annotations[num_annotations]; // 每个参数的注解表
} parameter_annotations[num_parameters];
}
用途:用于表示方法参数的运行时不可见注解。
结构:
javaRuntimeInvisibleParameterAnnotations_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u1 num_parameters; // 参数数量
{ u2 num_annotations; // 每个参数的注解数量
annotation annotations[num_annotations]; // 每个参数的注解表
} parameter_annotations[num_parameters];
}
用途:用于表示注解元素的默认值。
结构:
javaAnnotationDefault_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
element_value default_value; // 注解元素的默认值
}
用途:用于表示 invokedynamic 指令的引导方法。
结构:
javaBootstrapMethods_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 num_bootstrap_methods; // 引导方法数量
{ u2 bootstrap_method_ref; // 指向常量池中 CONSTANT_MethodHandle_info 的索引,表示引导方法
u2 num_bootstrap_arguments; // 引导方法参数数量
u2 bootstrap_arguments[num_bootstrap_arguments]; // 引导方法参数表
} bootstrap_methods[num_bootstrap_methods];
}
用途:用于表示方法参数的信息。
结构:
javaMethodParameters_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u1 parameters_count; // 参数数量
{ u2 name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示参数名称
u2 access_flags; // 参数的访问标志
} parameters[parameters_count];
}
用途:用于表示模块信息。
结构:
javaModule_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 module_name_index; // 指向常量池中 CONSTANT_Module_info 的索引,表示模块名称
u2 module_flags; // 模块的访问标志
u2 module_version_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示模块版本
u2 requires_count; // 依赖模块数量
{ u2 requires_index; // 指向常量池中 CONSTANT_Module_info 的索引,表示依赖模块
u2 requires_flags; // 依赖模块的访问标志
u2 requires_version_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示依赖模块版本
} requires[requires_count];
u2 exports_count; // 导出包数量
{ u2 exports_index; // 指向常量池中 CONSTANT_Package_info 的索引,表示导出包
u2 exports_flags; // 导出包的访问标志
u2 exports_to_count; // 导出包的目标模块数量
u2 exports_to_index[exports_to_count]; // 指向常量池中 CONSTANT_Module_info 的索引,表示导出包的目标模块
} exports[exports_count];
u2 opens_count; // 开放包数量
{ u2 opens_index; // 指向常量池中 CONSTANT_Package_info 的索引,表示开放包
u2 opens_flags; // 开放包的访问标志
u2 opens_to_count; // 开放包的目标模块数量
u2 opens_to_index[opens_to_count]; // 指向常量池中 CONSTANT_Module_info 的索引,表示开放包的目标模块
} opens[opens_count];
u2 uses_count; // 使用的服务数量
u2 uses_index[uses_count]; // 指向常量池中 CONSTANT_Class_info 的索引,表示使用的服务
u2 provides_count; // 提供的服务数量
{ u2 provides_index; // 指向常量池中 CONSTANT_Class_info 的索引,表示提供的服务
u2 provides_with_count; // 提供的服务实现数量
u2 provides_with_index[provides_with_count]; // 指向常量池中 CONSTANT_Class_info 的索引,表示提供的服务实现
} provides[provides_count];
}
用途:用于表示模块中的包信息。
结构:
javaModulePackages_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 package_count; // 包数量
u2 package_index[package_count]; // 指向常量池中 CONSTANT_Package_info 的索引,表示包
}
用途:用于表示模块的主类。
结构:
javaModuleMainClass_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 main_class_index; // 指向常量池中 CONSTANT_Class_info 的索引,表示主类
}
用途:用于表示嵌套类的宿主类。
结构:
javaNestHost_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 host_class_index; // 指向常量池中 CONSTANT_Class_info 的索引,表示宿主类
}
用途:用于表示嵌套类的成员类。
结构:
javaNestMembers_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 number_of_classes; // 成员类数量
u2 classes[number_of_classes]; // 指向常量池中 CONSTANT_Class_info 的索引,表示成员类
}
用途:用于表示记录类(Record Class)的信息。
结构:
javaRecord_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 components_count; // 组件数量
{ u2 name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示组件名称
u2 descriptor_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示组件描述符
u2 attributes_count; // 组件属性数量
attribute_info attributes[attributes_count]; // 组件属性表
} components[components_count];
}
这个属性用来支持Java16的记录类
用途:用于表示允许的子类。
结构:
javaPermittedSubclasses_attribute {
u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称
u4 attribute_length; // 属性的长度,不包括前6个字节
u2 number_of_classes; // 允许的子类数量
u2 classes[number_of_classes]; // 指向常量池中 CONSTANT_Class_info 的索引,表示允许的子类
}
用来支持Java15的密封类
上面总结了常见的JVM预定义属性,这些属性在Java类文件中起着关键作用,帮助JVM解析和加载类文件,并在运行时提供必要的信息。
本文作者:yowayimono
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!