- 类索引、父类索引与接口索引集合
- 字段表集合
- 方法表集合
- 属性表集合
由于上一篇文章中实例代码没有声明字段,所以本篇内容变更示例代码
源码
1 | package test; |
class文件
1 | CA FE BA BE(Class魔数) |
类索引(this_class)
作用
- 确定这个类的全限定名
- 指向CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串
位置
访问标志之后
内容
- 一个u2类型的数据
实例
1 | 00 01(1,this_class,类索引为1,常量池中第一项:test/Test) |
父类索引(super_class)
作用
- 确定这个类的父类全限定名
- 父类索引各自指向CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型的常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串
位置
- 类索引之后
内容
- 一个u2类型的数据
特点
- Java不允许多重继承,父类索引只有一个
- 除了
java.lang.Object
之外,所有类都有父类,父类都不为0
实例
1 | 00 03(3,super_class,父类索引为3,常量池中第三项:java/lang/Object) |
接口索引集合(interfaces)
作用
- 描述这个类实现了哪些接口
- 这些被实现的接口将按
implements
语句(如果类本身是一个接口,则应当是extends语句)后的接口顺序从左到右排列在接口索引集合中
- 这些被实现的接口将按
位置
父类索引之后
内容
- u2类型的数据:接口计数器interfaces_count
- 表示索引表的容量
- 如果没有实现任何接口,则为0
实例
1 | 00 00(0,interfaces_count,接口计数器,表示索引表的容量,0为没有实现任何接口) |
Class文件中由这三项数据来确定这个类的继承关系
字段表集合
作用
字段表field_info
,用来描述接口或者类中声明的变量
字段field
,包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量
位置
接口索引后
内容
- 字段的作用域(public、private、protected)
- 是实例变量还是类变量(static修饰符)
- 可变性(final)
- 并发可见性(volatile,是否强制从主内存读写)
- 是否被序列化(transient修饰符)
上述各个修饰符都是布尔值,用标志位来表示
- 字段名字
- 字段的数据类型
上述内容不固定,只能引用常量池中的常量来描述。
字段表结构见下表
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attributes_info | attributes | attributes_count |
access_flags字段访问标志见下表
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否public |
ACC_PRIVATE | 0x0002 | 字段是否private |
ACC_PROTECTED | 0x0004 | 字段是否protected |
ACC_STATIC | 0x0008 | 字段是否static |
ACC_FINAL | 0x0010 | 字段是否final |
ACC_VOLATILE | 0x0040 | 字段是否volatile |
ACC_TRANSIENT | 0x0080 | 字段是否transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动产生的 |
ACC_ENUM | 0x4000 | 字段是否enum |
特点
ACC_PUBLIC
、ACC_PRIVATE
、ACC_PROTECTED
三者只能选其一ACC_FINAL
、ACC_VOLATILE
二者只能选其一- 接口中字段必须有
ACC_PUBLIC
、ACC_STATIC
、ACC_FINAL
标志,Java语言规则决定。
说明
name_index
- 对常量池的引用
- 字段的简单名称
descriptor_index
- 对常量池的引用
- 字段和方法的描述符
- 全限定名
- test/TestClass
- 用
;
分隔- 简单名称
- 没有类型和参数修饰的方法或者字段名称
m
和inc
- 描述符
- 用来描述字段的数据类型、方法的参数列表(数量、类型、顺序)、返回值
- 描述符标识字符含义表
标识字符 | 含义 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | 对象类型,如Ljava/lang/Object |
- 基本数据类型及无返回值:大写字母
- 对象类型:L+对象的全限定名
- 数组:每一维度用一个前置的
[
int[]
>[I
java.lang.String[][]
>[[Ljava/lang/String;
方法:
- 顺序:先参数列表,后返回值
void inc()
>()V
java.lang.String toString()
>()Ljava/lang/String;
int testF(char[] args1, int arg2 , char[] arg3)
>([CI[C)I
- 顺序:先参数列表,后返回值
字段表中不会列出超类或者父接口中继承而来的字段
但有可能列出原本Java代码中不存在的字段
- 内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段
Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的
实例
1 | --------------字段表集合------------------- |
方法表集合
作用
method_info
,对方法的描述
位置
字段表之后
内容
方法表结构见下表
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attributes_info | attributes | attributes_count |
方法访问标志见下表
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 方法是否public |
ACC_PRIVATE | 0x0002 | 方法是否private |
ACC_PROTECTED | 0x0004 | 方法是否protected |
ACC_STATIC | 0x0008 | 方法是否static |
ACC_FINAL | 0x0010 | 方法是否final |
ACC_SYNCHRONIZED | 0x0020 | 方法是否synchronized |
ACC_BRIDGE | 0x0040 | 方法是否由编译器产生的桥接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定参数 |
ACC_NATIVE | 0x0100 | 方法是否native |
ACC_ABSTRACT | 0x0400 | 方法是否abstract |
ACC_STRICTFP | 0x0800 | 方法是否strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否由编译器自动产生 |
说明
方法里的代码,经过编译器编译成字节码指令后,存在方法属性表集合中一个名为
Code
的属性里面,之后有详解如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息
可能会出现由编译器自动添加的方法,之后有详解
- 最典型:类构造器
<clinit>
- 实例构造器
<init>
- 最典型:类构造器
根据虚拟机规范中对重载的定义(4.7.9),特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合(不包含返回值),也就是说无法只依靠返回值的不同来对一个已有方法进行重载。但是在Class文件格式中,特征签名只要描述符不是完全一致的两个方法也可以共存,也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同意Class文件中。
实例
1 | --------------方法表集合------------------- |
属性表集合
作用
attribute_info
,用于描述某些场景专有的信息
位置
方法表之后
内容
为了能正确解析Class文件,在最新的JVM规范中,预定义了一些属性4.7。
其中,4个属性对于Java正确解释类文件至关重要
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
StackMapTable | Code属性 | 方法的局部变量描述 |
BootstrapMethods | 类文件 | JDK1.7新增,用于保存invokedynamic指令引用的引导方法限定符 |
其中,9个属性对JVM解释Class文件不重要,但对Java SE的类库或者工具解释Class文件有用,可选
属性名称 | 使用位置 | 含义 |
---|---|---|
Exceptions | 方法表 | 方法抛出的异常 |
InnerClasses | 类文件 | 内部类列表 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类时才能拥有这个属性,标识着歌类所在的外围方法 |
Synthetic | 类、方法表、字段表 | 标识方法或字段为编译器自动生成的 |
Signature | 类、方法表、字段表 | JDK1.5新增,用于支持泛型情况下的方法签名。在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,这个属性会记录泛型签名信息。 |
SourceFile | 类文件 | 记录源文件名称 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | 类 | 方法的局部变量描述 |
LocalVariableTypeTable | 类 | JDK1.5新增,它使用特征签名代替描述符,是为了引入泛型语法后能描述泛型参数化类型而添加 |
其中,13个属性对JVM解释Class文件不重要,但包含有关类文件的元数据,这些元数据要么由Java SE平台的类库公开,要么由工具提供,可选
属性名称 | 使用位置 | 含义 |
---|---|---|
SourceDebugExtension | 类 | JDK1.6新增,用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并运行在JVM中的程序提供了一个进行调试的标准机制。 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 |
RuntimeVisibleAnnotations | 类、方法表、字段表 | JDK1.5新增,为动态注解提供支持,用于指明哪些注解是运行时(反射调用)可见的 |
RuntimeInvisibleAnnotations | 类、方法表、字段表 | JDK1.5新增,为动态注解提供支持,用于指明哪些注解是运行时(反射调用)不可见的 |
RuntimeVisibleParameterAnnotations | 方法表 | JDK1.5新增,用于指明哪些方法参数是运行时(反射调用)可见的 |
RuntimeInvisibleParameterAnnotations | 方法表 | JDK1.5新增,用于指明哪些方法参数是运行时(反射调用)不可见的 |
RuntimeVisibleTypeAnnotations | 类、方法表、字段表、Code属性 | 可变长度属性,记录使用的类型运行时可见注释。还记录泛型类、接口、方法和构造函数的类型参数声明的注释。最多有一个。 |
RuntimeInvisibleTypeAnnotations | 类、方法表、字段表、Code属性 | 可变长度属性,记录使用的类型运行时不可见注释。还记录泛型类、接口、方法和构造函数的类型参数声明的注释。最多有一个。 |
AnnotationDefault | 方法表 | 可变长度属性,记录使用的注释类型元素的默认值,最多只有一个 |
MethodParameters | 方法表 | 可变长度属性,记录有关方法的形式参数的信息,例如它们的名称,最多只有一个 |
Module | 类的属性表 | 可变长度属性,记录模块所需的模块;模块导出和打开的包;以及模块使用和提供的服务,最多只有一个 |
ModulePackages | 类的属性表 | 可变长度属性,记录模块属性导出或打开的模块的所有包,以及记录在Module属性中的服务实现的所有包,还可以记录模块中既不导出也不打开或包含服务实现的包 |
ModuleMainClass | 类的属性表 | 固定长度属性,记录模块的主类 |
属性表结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
说明
不要求各个属性表具有严格顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,JVM运行时会忽略掉不认识的属性
每个属性的名称需要从常量池中引用一个
CONSTANT_Utf8_info
类型的常量来表示,属性之的结构可以完全自定义,只需要通过一个u4长度的属性说明占用的位数即可。- 具体属性格式可查询JVM规范(4.7.2-4.7.27)
实例
1 | ---Code属性开始--- |
Slot
:JVM为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型(byte、char、float、int、short、boolean和returnAddress)占用1Slot,64位的数据类型(double、long)需要2Slot来存放。当代码执行超过一个局部变量的作用域时,这个局部变量所占的Slot可以被其他局部变量所使用,Javac编译器会根据变量的作用与来分配Slot给各个变量使用,然后计算max_locals的大小.
工具
Chrome Plugin-Binary Converter