编辑
2024-08-02
后端
00
请注意,本文编写于 281 天前,最后修改于 281 天前,其中某些信息可能已经过时。

目录

1. Code
2. ConstantValue
3. Exceptions
4. InnerClasses
5. EnclosingMethod
6. Synthetic
7. Signature
8. SourceFile
9. LineNumberTable
10. LocalVariableTable
11. LocalVariableTypeTable
12. Deprecated
13. RuntimeVisibleAnnotations
14. RuntimeInvisibleAnnotations
15. RuntimeVisibleParameterAnnotations
16. RuntimeInvisibleParameterAnnotations
17. AnnotationDefault
18. BootstrapMethods
19. MethodParameters
20. Module
21. ModulePackages
22. ModuleMainClass
23. NestHost
24. NestMembers
25. Record
26. PermittedSubclasses

上节我们分析了class类文件结构并分析了class文件常量池和17种不同的常量项,接下来再回到这个结构

go
ClassFile { 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虚拟机规范给出了对应的结构 首先是字段结构

go
Field_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 属性(用于表示静态常量的初始值)。

我们可以看到字段也是有属性表的

接下来是方法结构

go
Method_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 属性(用于表示方法声明抛出的异常)等。

方法也是有属性表的,接下来我们来看一下属性表中属性的结构是怎样的

go
attribute_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用的或者只有方法或者字段能用的属性,也有字段和方法类都能用的属性,接下来总结一下这些属性和他们的结构还有作用

1. Code

用途:用于描述方法的字节码指令、异常表、局部变量表等信息。

结构

java
Code_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属性信息的属性

2. ConstantValue

用途:用于表示静态常量的初始值。

结构

java
ConstantValue_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 constantvalue_index; // 指向常量池中常量值的索引 }

3. Exceptions

用途:用于表示方法声明抛出的异常。

结构

java
Exceptions_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 的索引,表示异常类型 }

4. InnerClasses

用途:用于描述内部类信息。

结构

java
InnerClasses_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]; }

举个例子,假如有以下类

java
public 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属性可能会看起来像这样:

go
InnerClasses_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` 访问权限 } }

5. EnclosingMethod

用途:用于描述局部类或匿名类所在的 enclosing 方法。

结构

java
EnclosingMethod_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 方法 }

这个我们也举个例子,假如有以下类

java
public 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属性可能如下:

go
EnclosingMethod_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` }

6. Synthetic

用途:用于标记编译器生成的类成员(如桥方法)。

结构

java
Synthetic_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 }

这个属性用来描述一些由编译器生成的类或者方法,如桥接方法,桥接方法是编译器为了完成某些操作生成的,如泛型的类型擦除,编译器会生成一个类型擦除后的方法

7. Signature

用途:用于表示泛型类型信息。

结构

java
Signature_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 signature_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示签名 }

8. SourceFile

用途:用于表示源文件的名称。

结构

java
SourceFile_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 sourcefile_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示源文件名称 }

9. LineNumberTable

用途:用于调试,表示字节码指令与源代码行号的对应关系。

结构

java
LineNumberTable_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属性的附加属性之一

10. LocalVariableTable

用途:用于调试,表示局部变量表的信息。

结构

java
LocalVariableTable_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的附加属性

11. LocalVariableTypeTable

用途:用于调试,表示局部变量表的泛型类型信息。

结构

java
LocalVariableTypeTable_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]; }

12. Deprecated

用途:用于标记类、字段或方法已过时。

结构

java
Deprecated_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 }

13. RuntimeVisibleAnnotations

用途:用于表示运行时可见的注解。

结构

java
RuntimeVisibleAnnotations_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 num_annotations; // 注解数量 annotation annotations[num_annotations]; // 注解表 }

14. RuntimeInvisibleAnnotations

用途:用于表示运行时不可见的注解。

结构

java
RuntimeInvisibleAnnotations_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 num_annotations; // 注解数量 annotation annotations[num_annotations]; // 注解表 }

15. RuntimeVisibleParameterAnnotations

用途:用于表示方法参数的运行时可见注解。

结构

java
RuntimeVisibleParameterAnnotations_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]; }

16. RuntimeInvisibleParameterAnnotations

用途:用于表示方法参数的运行时不可见注解。

结构

java
RuntimeInvisibleParameterAnnotations_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]; }

17. AnnotationDefault

用途:用于表示注解元素的默认值。

结构

java
AnnotationDefault_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 element_value default_value; // 注解元素的默认值 }

18. BootstrapMethods

用途:用于表示 invokedynamic 指令的引导方法。

结构

java
BootstrapMethods_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]; }

19. MethodParameters

用途:用于表示方法参数的信息。

结构

java
MethodParameters_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]; }

20. Module

用途:用于表示模块信息。

结构

java
Module_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]; }

21. ModulePackages

用途:用于表示模块中的包信息。

结构

java
ModulePackages_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 package_count; // 包数量 u2 package_index[package_count]; // 指向常量池中 CONSTANT_Package_info 的索引,表示包 }

22. ModuleMainClass

用途:用于表示模块的主类。

结构

java
ModuleMainClass_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 main_class_index; // 指向常量池中 CONSTANT_Class_info 的索引,表示主类 }

23. NestHost

用途:用于表示嵌套类的宿主类。

结构

java
NestHost_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 host_class_index; // 指向常量池中 CONSTANT_Class_info 的索引,表示宿主类 }

24. NestMembers

用途:用于表示嵌套类的成员类。

结构

java
NestMembers_attribute { u2 attribute_name_index; // 指向常量池中 CONSTANT_Utf8_info 的索引,表示属性名称 u4 attribute_length; // 属性的长度,不包括前6个字节 u2 number_of_classes; // 成员类数量 u2 classes[number_of_classes]; // 指向常量池中 CONSTANT_Class_info 的索引,表示成员类 }

25. Record

用途:用于表示记录类(Record Class)的信息。

结构

java
Record_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的记录类

26. PermittedSubclasses

用途:用于表示允许的子类。

结构

java
PermittedSubclasses_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 许可协议。转载请注明出处!