JVM-Class文件结构
Posted jackqiang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM-Class文件结构相关的知识,希望对你有一定的参考价值。
Class文件结构
class文件
- class文件是一组以8个字节为基础单位的二进制流
- 各个数据项目严格按照顺序紧凑地排序在文件之中,中间没有添加任何分隔符
- 当数据项需要占用8个以上字节位的时候,按照高位在前的方式分割成若干个8个字节存储
class文件格式
class文件格式采用的是类似于C语言结构体的伪结构来存储数据,这种伪结构种只有两种数据类型:“无符号型”和表
- 无符号型 无符号型属于基本的数据类型,以u1、u2、u4、u8来分别代表一个字节,两个字节,四个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串
- 表 表是有多个无符号数或者其他表作为数据项构成的复合数据类型,为了方便区分,所有表的命名都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据。需要注意的是class文件是没有分隔符的,所以每个的二进制数据类型都是严格定义的。具体的顺序定义如下。
类型 | 名称 | 数量 |
---|---|---|
u4 | magic(魔数) | 1 |
u2 | minor_version(次版本号) | 1 |
u2 | major_version(主版本号) | 1 |
u2 | constant_pool_count(常量池数量) | 1 |
cp_info | constant_pool(常量池表) | constant_pool_count-1 |
u2 | access_flags(类的访问权限控制) | 1 |
u2 | this_class(类名) | 1 |
u2 | super_class(父类名) | 1 |
u2 | interfaces_counts(接口数量) | 1 |
u2 | interfaces(接口名) | interfaces_count |
u2 | fileds_count(域数量) | 1 |
file_info | fileds(域名) | fields_count |
u2 | methods_counts(方法数量) | 1 |
method_info | methods(方法名) | methods_count |
u2 | attributes_count(属性数量) | 1 |
attribute_info | attributes(属性) | attributes_count |
魔数
- 每个Class文件的头4个字节代表魔数
- 魔数的唯一作用就是确定这个文件是否为一个能被虚拟机接受的class文件
- 魔数的值CAFEBABE
版本号
Class的版本号是跟在魔数后面的
- 第五和第六个字节代表次版本号
- 第七和第八个字节代表主版本号
Java的版本号是从45开始的,每个JDK大版本发布主版本号向上加1,高版本兼容低版本,但是不能运行高于虚拟机版本号的Class文件。
JDK版本 | 16进制 | 版本号 |
---|---|---|
1.2 | 0X002E | 46 |
1.3 | 0X002F | 47 |
1.4 | 0X0030 | 48 |
1.5 | 0X0031 | 49 |
1.6 | 0X0032 | 50 |
1.7 | 0X0033 | 51 |
1.8 | 0X0034 | 52 |
9 | 0X0035 | 53 |
10 | 0X0036 | 54 |
常量池
常量池是紧接着主、次版本号之后的,可以简单的理解为Class文件资源仓库
- 是Class文件结构种于其他项目关联最多的数据
- 是占用Class文件空间最大的数据项目之一
- 是在Class文件种第一个出现表类型的数据项目
由于常量池种常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量容量计数值(constant_pool_count),这个容量计数值是从1开始而不是0,是因为如果后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示;入上图所示2C,十进制45 代表着常量吃中有44个常量 索引1~44
Class文件结构中只有常量池容量计数器是从1开始。对于其他的集合类型,包括接口索引集合、字段表集合等都是从0开始
package com.jack.test;
public class TestClass {
private int m;
public int inc(){
return m + 1;
}
}
上图是二进制class中可以看出,0X0013表示的是19,常量的数量是18个,通过javap 命令可以打印是18个常量
E:learnjavaalgs-data-structuresrcmainjavacomjack est>javap -verbose T
estClass.class
Classfile /E:/learn/java/algs-data-structure/src/main/java/com/jack/test/TestC
lass.class
Last modified 2020-7-19; size 289 bytes
MD5 checksum a14137a5f8affdff932d5fb251c12a09
Compiled from "TestClass.java"
public class com.jack.test.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#16 // com/jack/test/TestClass.m:I
#3 = Class #17 // com/jack/test/TestClass
#4 = Class #18 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 inc
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 TestClass.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = NameAndType #5:#6 // m:I
#17 = Utf8 com/jack/test/TestClass
#18 = Utf8 java/lang/Object
常量池中两大类常量
常量池中主要存放两大常量类:字面常量、符号引用
字面常量:和Java语言层面的常量概念差不多,文本字符串、被声明为final的常量
符号引用:属于编译原理方面的概念
- 被模块导出或者开放的包(Package)
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符
- 方法的名称和描述符
- 方法句柄和方法类型
- 动态调用点和动态常量
常量池表结构
常量池中每一项常量都是一个表,每个表代表着不同类型的常量,这些表有一个共同的特性,就是表结构的起始第一位是一个u1类型的标志位,来表示当前常量是属于那种常量类型。常量池表结构如下图所示:
访问标志
访问标志是跟在常量池结束之后的2个字节代表,主要用于识别一些类或者接口层次的访问信息。
比如:这个Class是类还是接口;是否定义为public类;是否定义为abstract类型;如果是类的话是否声明为final等等
图片中只定义了九个标志位,没有使用的的标志位一律要求为零;具体如下图所示:
类索引、父类索引与接口索引集合
类索引和父类索引
1、类索引和父类索引都是一个u2类型的数据;
2、类索引 用来确定这个类的全限定名,父类索引用来确定这个类的父类全限定名;
3、因为在Java中单继承,父类的索引只有一个,除了java.lang.Object外,所有的Java类都有父类。
4、各自指向一个类型CONSTANT_Class_info的类描述符常量
接口索引集合
- 接口索引集合是一个u2类型的数据集合;
- 描述这个类实现了哪些接口
- 被实现的接口将按照implements关键字从左到右排列在接口索引集合中
- 接口索引第一项数据是接口计数器,表示索引的容量,如果该类没有实现任何接口,那么接口计数器值为0
字段表集合
字段表用来描述接口或者类中声明的变量,包括类级变量和实例变量,但是不包括方法内部声明的局部变量
字段的表结构:
字段的访问标志(access_flags)
简单名称、描述符(name_index、descriptor_index)
跟随access_flags后面的两项索引值:name_index、descriptor_index分别代表着字段的简单名称以及字段和方法的描述符
简单名称:就是指没有类型和参数修饰的方法或者字段名称
描述符:用来描述字段数据类型、方法参数列表(数量、类型、顺序)和返回值
根据描述符规则,基础数据类型以及void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示
描述符标志字符表:
对于数据,每一个维度用一个前置“[”来表示
如定义一个java.lang.String[] 对应的就是“[Ljava/lang/String;”;如果是二维数据 “[[Ljava/lang/String;”
描述符用来描述方法时,按照先参数列表、后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号中“()”之类;
比如:void inc() --->"()V"
? int inc (Char[] str,int i) ---> ([CI)I
在descriptor_index后面跟随的是一个属性集合表,主要用来存储一些额外的信息,这个在后面会详细的写
方法表集合
方法表集合的表结构和字段表结构差不多,仅在访问标志和属性表集合有所区别
方法表访问标志:
特征签名:Java方法特征签名只包括方法名称、参数顺序和参数类型,在字节码的特征签名还包括方法返回值和受异常表
在Java语言中,方法是存在重载的,重载的定义是方法名相同,参数列表不同,返回值可以相同也可以不同。
要重载一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名。所以在java中方法重载和返回值无关。
属性表集合
属性表在Class文件、字段表、方法表中都可以携带自己的属性表集合,以描述某些场景专有的信息。与Class文件中其他的数据项目要求严格的舒徐、长度和内容不同。属性表集合相对稍微宽松一些,允许只要已有属性名不重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息。
虚拟机规范定义的属性
这些图片都是网上找的,一个一个太难写了,这些都是JDK1.7之前的,在之后还有新增一些其他的,具体的参看相关书籍和文档
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的格式则是完全自定义,只需要通过一个u4的长度属性去说明属性值所占用的位数即可
code属性
在Java程序方法体中的代码经过javac编译之后,最终为字节码指令存储在Code属性中,Code属性出现在方法表的属性中,但是并不是一定就存在的,比如抽象类和接口中就不存在Code属性
Code属性表结构
- attribute_name_index:一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为”Code“,它代表该属性的属性名称。
- attribute_length:属性值的长度,由于属性名称索引与属性长度一共6个字节,所以属性值长度=属性表长度-6
- max_stack:代表了操作数栈(Operand Stacks)深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个最大值。虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度
- max_locals:局部变量表所需要的存储空间。单位是Slot
- code_length:字节码长度
- code:编译后生成的字节码指令
- exception_table:包含4个字段(start_pc、end_pc、handler_pc、catch_type)。这些字段的含义是:当字节码在start_pc行到end_pc行之间(try的范围)出现了类型为catch_type的异常或其子类的异常(catch_type为指向一个CONSTANT_Class_info型常量的索引),则跳转到handler_pc行继续处理。
方法的字节指令
源码
package com.jack.test;
public class TestClass {
private int m;
public int inc(){
return m + 1;
}
public static void test(int i){
}
}
编译之后的方法字节码指令
{
public com.jack.test.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 7: 0
public static void test(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 12: 0
}
从编译之后的字节码可以看出args_size=1,这个是为什么呢,方法是没有传参的。在Java中有一个关键字 this访问到此方法的对象属性。this关键字的访问转变为对一个普通方法参数的访问,然后在虚拟机调用实例方法时自动传入此参数而已。因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部标量表中也会预留出来第一个变量槽位来存放对象实例引用,所以实例方法参数值从1开始计算。这个只是对实例方法有效,如果是static(静态方式)那么args_size=0而不是1;从上如的中test()方法中可以看出。
Exceptions属性
? Exception属性是和Code属性平级的一项属性,它的作用是列举处方法中可能抛出的受查异常(Checked Expcetions),也就是方法描述是在throws关键字后面列举的异常。
Exception属性结构:
number_of_exceptions表示方法可能抛出number_of_exceptions中受查异常,每一种受查异常用一个exception_index_table项表示,exception_index_table是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型
LineNumberTable属性
? LineNumberTable属性用于描述java源码行号和字节码行号之间的对应关系。它并不是运行时必须的属性,但默认会生成到Class文件中,可以再javac中分别使用-g:none或-g:lines选项取消或要求生成这项信息。如果选择不生成LineNumberTable属性,当程序抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行设置断点
LineNumberTable属性结构:
line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合,line_number_info表包含了start_pc和line_number两个u2类型的数据项,前者表示字节码行号,后者表示java源码行号
LocalVariableTable属性
? LocalVariableTable属性用于描述栈帧中局部变量表中的变量与java源码中定义的变量的关系,它也不是运行时必须数据,但默认会生成在Class文件中。可以在javac时使用-g:none或-g:lines来取消或要求生成这项信息。如果不生成这个信息,当其他人引入这个方法时,所有参数名称会丢失,IDE会使用诸如arg1、arg2之类的占位符替代原有的参数名,这对程序运行没有影响,但是对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获取参数值。
LocalVariableTable属性结构:
其中local_variable_info 项代表了一个栈帧与源码中局部变量的关联,local_variable_info表结构如下所示:
SourceFile属性
? SourceFile属性用于记录生成这个Class文件的源码名称。这个属性也是可选的,可以分别使用javac 的 -g :none或-g:source来关闭或要求生成这项信息。如果不生成这项信息,当抛出异常,堆栈中将不会显示出错代码所属的文件名。这个属性是一个定长的属性。
SourceFile属性结构:
sourcefile_index数据项时指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件名
ConstantValue属性
ConstantValue属性主要的作用是通知虚拟机自动为静态变量赋值。只有static修饰的变量(类变量)才可以使用这项属性。
比如: int=123 和static int = 234
虚拟机对这两种变量赋值的方式和时刻是有所不同的:
-
对于非static类型的变量(也就是实例变量)的赋值是在实例构造器
()方法中进行的 -
对于类变量则有两种方式可以选择:在类构造器
()或者ConstantValue中进行 对于目前oracle公司的javac来编译:
1、如果同时使用fianl和static来修饰一个变量,并且这个变量的数据类型和基本类型或是java.lang.String类型,将会使用ConstantValue属性来进行初始化
2、如果这个变量没有被final修饰,或者是非基本类型及字符串,则将会选择在
()方法中进行初始化
public class TestClass {
int x = 123;
static int y = 234;
final static int m = 456;
}
Last modified 2020-7-26; size 320 bytes
MD5 checksum e282601d538c42886aba938a359a39ca
Compiled from "TestClass.java"
public class com.jack.test.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#16 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#17 // com/jack/test/TestClass.x:I
#3 = Fieldref #4.#18 // com/jack/test/TestClass.y:I
#4 = Class #19 // com/jack/test/TestClass
#5 = Class #20 // java/lang/Object
#6 = Utf8 x
#7 = Utf8 I
#8 = Utf8 y
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 <clinit>
#14 = Utf8 SourceFile
#15 = Utf8 TestClass.java
#16 = NameAndType #9:#10 // "<init>":()V
#17 = NameAndType #6:#7 // x:I
#18 = NameAndType #8:#7 // y:I
#19 = Utf8 com/jack/test/TestClass
#20 = Utf8 java/lang/Object
{
int x;
descriptor: I
flags:
static int y;
descriptor: I
flags: ACC_STATIC
public com.jack.test.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 123
7: putfield #2 // Field x:I
10: return
LineNumberTable:
line 3: 0
line 4: 4
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: sipush 234
3: putstatic #3 // Field y:I
6: return
LineNumberTable:
line 6: 0
}
SourceFile: "TestClass.java"
E:learnjavaalgs-data-structuresrcmainjavacomjack est>javac TestClass.java
E:learnjavaalgs-data-structuresrcmainjavacomjack est>javap -verbose TestClass.class
Classfile /E:/learn/java/algs-data-structure/src/main/java/com/jack/test/TestClass.class
Last modified 2020-7-26; size 361 bytes
MD5 checksum 96131d509457480815063e6bce4cdbde
Compiled from "TestClass.java"
public class com.jack.test.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#19 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#20 // com/jack/test/TestClass.x:I
#3 = Fieldref #4.#21 // com/jack/test/TestClass.y:I
#4 = Class #22 // com/jack/test/TestClass
#5 = Class #23 // java/lang/Object
#6 = Utf8 x
#7 = Utf8 I
#8 = Utf8 y
#9 = Utf8 m
#10 = Utf8 ConstantValue
#11 = Integer 456
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 <clinit>
#17 = Utf8 SourceFile
#18 = Utf8 TestClass.java
#19 = NameAndType #12:#13 // "<init>":()V
#20 = NameAndType #6:#7 // x:I
#21 = NameAndType #8:#7 // y:I
#22 = Utf8 com/jack/test/TestClass
#23 = Utf8 java/lang/Object
{
int x;
descriptor: I
flags:
static int y;
descriptor: I
flags: ACC_STATIC
static final int m;
descriptor: I
flags: ACC_STATIC, ACC_FINAL
ConstantValue: int 456
public com.jack.test.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 123
7: putfield #2 // Field x:I
10: return
LineNumberTable:
line 3: 0
line 4: 4
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: sipush 234
3: putstatic #3 // Field y:I
6: return
LineNumberTable:
line 6: 0
}
SourceFile: "TestClass.java"
InnerClasses属性
InnerClasses属性是用来记录内部类和宿主类之间的关联。如果一个类中定义了内部类,那么编译器将会为它以及它包含的内部类生成InnerClasses属性。
InnerClasses属性表结构:
number_of_classes记录类中有多少个内部类,每个内部类的信息都是由一个inner_classes_info属性表来描述。
inner_classes_info属性表结构:
inner_class_info_index和outer_class_info_index都指向常量池中CONSTANT_Class_info常量索引,分别代表了内部类和宿主类的符号引用。
inner_name_index:指向常量池中CONSTANT_Utf8_info常量索引,代表这个内部类中的名称,如果是匿名内部类,这项值为0
inner_class_access_flags:内部类的访问标志,类似于access_flags,取值范围如下图所示:
Deprecated与Synthetic属性
Deprecated和Synthetic两个属性都属于标志类型的布尔类型,只存在有和没有区别,没有属性值的概念。
Deprecated:用于表示某个类、字段、方法,已经被程序作者定义为不推荐使用,过时了。在java代码中使用@Deprecated注解来标志
Synthetic:代表此字段或者方法不是有Java源码直接产生的,而是由编译器自行添加的
Deprecated与Synthetic属性结构:
StackMapTable属性
StackMapTable属性在JDK1.6增加到Class文件规范中的,它是一个相当复杂的边长属性,位于Code属性中。这个序性会在虚拟机加载的字节码验证阶段被新类型检查器使用。母的在于代替之前比较消耗性能的基于数据流分析的类型推导验证器。
StackMapTable属性:
- 包含零至多个栈映射帧
- 每个栈映射帧都显示或者隐式地代表一个字节码偏移量,用于表示执行到该字节码时局部变量表和操作数栈的验证类型
类型检查器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。
StackMapTable属性结构:
Signature属性
Signature属性在JDK1.5发布后增加到Class文件规范中,它是一个可选的定长属性,可以出现于类、属性表和方法表结构的属性表中。在任何类、接口、初始化方法或成员的泛型简明中如果包含了类型变量(TypeVariable)或参数化类型(Parameterized Type),则Signature属性会为它记录泛型签名信息。
使用Signature属性去记录泛型类型的原因是:
-
Java语言的泛型采用的是擦除法实现的伪泛型
-
字节码(Code属性)中所有的泛型信息编译(类型变量、参数化类型)在编译之后都通通擦除。使用擦除法好处是实现简单,但是坏处是运行期间无法像C#等有真泛型
Signature属性结构:
BootstrapMethods属性
? BootstrapMethods属性是在JDK1.7发布后增加到CLass文件规范中,它是一个复杂的变长属性,位于类文件表中。这个属性用于保存invokedynamic指令引用的引导方法限定符。
? 如果某个类文件的常量池中曾经出现过CONSTANT_InvokeDynamic_info类型的常量,那么这个类文件的属性表中必须存在一个明确的BootstrapMethods属性,另外,即使CONSTANT_InvokeDynamic_info类型的常量在常量池中出现过多次,最多也只能有一个BootstrapMethods属性。
BootstrapMethods属性表结构:
其中引用到的bootstrap_method结构如下:
BootstrapMethod属性中的num_bootstrap_methods项的值是一个bootstrap_methods[]数组中的引导方法限定符的数量,bootstrap_methods[]数组中的每个成员包含了一个指向常量池CONSTANT_MethodHandle结构的索引值,它代表一个引导方法。还包含了这个引导方法静态参数的序列。
bootstrap_methods[]数组的每个成员必须包含以下三项:
bootstrap_method_ref:该项的值必须是一个对常量池的有效索引,常量池在该索引处的值必须是一个CONSTANT_MethodHandle_info结构
num_bootstrap_arguments:该项的值给出了bootstrap_arguments[]数组成员数量
bootstrap_arguments[]:该数组的每个成员必须是一个常量池的有效索引
MethodParameters属性
MethodParameters属性是在JDK1.8新加入到Class文件格式中的,它是一个用在方法表表中的变长属性。MethodParameters的作用是记录方法的各个形参名称和信息。
MethodParameters属性结构:
类型 | 数量 | |
---|---|---|
u2 | attribure_name_index | 1 |
u4 | attribute_length | 1 |
u1 | parameters_count | 1 |
parameter | parameters | parameters_count |
其中引用到的parameter结构如下:
类型 | 名称 | 数量 |
---|---|---|
u2 | name_index | 1 |
u2 | access_flag | 1 |
name_index:是一个指向常量池CONSTANT_Utf8_info常量的索引
access_flag:是参数的状态指示器,它可以包含以下三种状态中的一种或者多种
- 0x0010(ACC_FINAL):表示该参数被final修饰
- 0x1000(ACC_SYNTHETIC):表示该参数没有出现在源文件中,是编译器自动生成
- 0x8000(ACC_MANDATED):表示该参数在源文件中隐式定义的。
模块化相关属性
JDK9中一个重量级的功能是Java的模块化功能,因为模块描述文件最终是要编译成一个独立的Class文件来存储的,所以Class文件格式也扩展了Module、ModulePackages和ModuleMainClass三个属性用于支持Java模块化功能。
Module属性
? Module属性是一个非常复杂的边长属性,除了表示该模块的名称、版本、标志信息以外,还存储了这个模块requires、exports
opens、uses和provides定义的全部内容。
? Module属性表结构:
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | module_name_index | 1 |
u2 | module_flags | 1 |
u2 | module_versin_index | 1 |
u2 | requires_count | 1 |
require | requires | require_count |
u2 | exports_count | 1 |
export | exports | export_count |
u2 | opens_count | 1 |
open | opens | open_count |
u2 | uses_count | 1 |
use | uses_index | ues_count |
u2 | provides_count | 1 |
provide | provides | provide_count |
exports属性
exports属性的每一个元素都代表一个被模块所导出的包。exports属性表结构如下所示:
类型 | 名称 | 数量 |
---|---|---|
u2 | exports_index | 1 |
u2 | exports_flags | 1 |
u2 | exports_to_count | 1 |
export | exports_to_index | exports_to_count |
ModulePackagess属性
ModulePackagess属性是另一个用于支持Java模块化的变长属性,用于描述该模块中所有的包,不管是不是export或者open的
ModulePackagess属性结构如下:
类型 | 名称 | 描述 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | package_count | 1 |
u2 | package_index | package_count |
参考:深入理解Java虚拟机第三版
以上是关于JVM-Class文件结构的主要内容,如果未能解决你的问题,请参考以下文章