文章目录
类索引、父类索引、接口索引集合
· 在访问标记后,会指定该类的类别、父类类别以及实现的接口,格式如下:
· 这三项数据来确定这个类的继承关系。
· 类索引用于确定这个类的全限定名
· 父类索引用于确定这个类的父类的全限定名。由于 Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object 之外,所有的Java类都有父类,因此除了java.lang.Object 外,所有Java类的父类索引都不为 0。
· 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按 implements 语句(如果这个类本身是一个接口,则应当是 extends 语句)后的接口顺序从左到右排列在接口索引集合中。
1.this_class(类索引)
2字节无符号整数,指向常量池的索引。它提供了类的全限定名,如com/atguigu/java1/Demo。this_class的值必须是对常量池表中某项的一个有效索引值。常量池在这个索引处的成员必须为CONSTANT_Class_info类型结构体,该结构体表示这个class文件所定义的类或接口。
2.super_class (父类索引)
· 2字节无符号整数,指向常量池的索引。它提供了当前类的父类的全限定名。如果我们没有继承任何类,其默认继承的是java/lang/Object类。同时,由于Java不支持多继承,所以其父类只有一个。
· superclass指向的父类不能是final。
3. interfaces
· 指向常量池索引集合,它提供了一个符号引用到所有已实现的接口
· 由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_Class (当然这里就必须是接口,而不是类)。
3.1 interfaces_count (接口计数器)
interfaces_count项的值表示当前类或接口的直接超接口数量。
**3.2 interfaces
interfaces []中每个成员的值必须是对常量池表中某项的有效索引值,它的长度为 interfaces_count。 每个成员 interfaces[i]必须为 CONSTANT_Class_info结构,其中 0 <= i < interfaces_count。在 interfaces[]中,各成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即 interfaces[0]对应的是源代码中最左边的接口。
字段表集合
fields
· 用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。(local variables)
· 字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
· 它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、private或protected)、是类变量还是实例变量(static修饰符)、是否是常量(final修饰符)等。
注意事项:
· 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
· 在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。
字段计数器
fields_count的值表示当前class文件fields表的成员个数。使用两个字节来表示。
fields表中每个成员都是一个field_info结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段。
字段表
fields []****(字段表)
· fields表中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。
· 一个字段的信息包括如下这些信息。这些信息中,各个修饰符都是布尔值,要么有,要么没有。
>作用域(public、private、protected修饰符)
>是实例变量还是类变量(static修饰符)
>可变性(final)
>并发可见性(volatile修饰符,是否强制从主内存读写)
>可否序列化(transient修饰符)
>字段数据类型(基本数据类型、对象、数组)
>字段名称
· 字段表结构
字段表作为一个表,同样有他自己的结构:
字段表集合
fields
· 用于描述接口或类中声明的变量。字段(field)包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量。(local variables)
· 字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
· 它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、private或protected)、是类变量还是实例变量(static修饰符)、是否是常量(final修饰符)等。
注意事项:
· 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
· 在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。
字段计数器
fields_count的值表示当前class文件fields表的成员个数。使用两个字节来表示。
fields表中每个成员都是一个field_info结构,用于表示该类或接口所声明的所有类字段或者实例字段,不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段。
字段表
fields []****(字段表)
· fields表中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。
· 一个字段的信息包括如下这些信息。这些信息中,各个修饰符都是布尔值,要么有,要么没有。
>作用域(public、private、protected修饰符)
>是实例变量还是类变量(static修饰符)
>可变性(final)
>并发可见性(volatile修饰符,是否强制从主内存读写)
>可否序列化(transient修饰符)
>字段数据类型(基本数据类型、对象、数组)
>字段名称
· 字段表结构
字段表作为一个表,同样有他自己的结构:
方法表集合
方法表集合
methods:指向常量池索引集合,它完整描述了每个方法的签名。
· 在字节码文件中,每一个method_info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public、private或protected),方法的返回值类型以及方法的参数信息等。
· 如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来。
· 一方面,methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。另一方面,methods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息(比如:类(接口)初始化方法()和实例初始化方法())。
使用注意事项:
在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个class文件中。
也就是说,尽管Java语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,但是和Java语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。
方法计数器
methods_count的值表示当前class文件methods表的成员个数。使用两个字节来表示。
methods 表中每个成员都是一个method_info结构。
方法表
methods [](方法表)
· methods表中的每个成员都必须是一个method_info结构,用于表示当前类或接口中某个方法的完整描述。如果某个method_info结构的access_flags项既没有设置 ACC_NATIVE 标志也没有设置ACC_ABSTRACT标志,那么该结构中也应包含实现这个方法所用的Java虚拟机指令。
· method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法
· 方法表的结构实际跟字段表是一样的,方法表结构如下:
属性表集合
属性表集合(attributes)
方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该 class 文件的源文件的名称。以及任何带有RetentionPolicy.CLASS 或者RetentionPolicy.RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试,一般无须深入了解。
此外,字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息。
属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性。
属性计数器
attributes_count的值表示当前class文件属性表的成员个数。属性表中每一项都是一个attribute_info结构。
属性表
attributes [](属性表)
属性表的每个项的值必须是attribute_info结构。属性表的结构比较灵活,各种不同的属性只要满足以下结构即可。
· ① ConstantValue 属性
① ConstantValue 属性
ConstantValue 属性表示一个常量字段的值。位于 field_info结构的属性表中。
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;//字段值在常量池中的索引,常量池在该索引处的项给出该属性表示的常量值。(例如,值是long型的,在常量池中便是CONSTANT_Long)
}
② Deprecated 属性
Deprecated 属性是在 JDK 1.1 为了支持注释中的关键词@deprecated 而引入的。
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
③ Code 属性
Code属性就是存放方法体里面的代码。但是,并非所有方法表都有Code属性。像接口或者抽象方法,他们没有具体的方法体,因此也就不会有Code属性了。
Code属性表的结构,如下图:
可以看到:Code属性表的前两项跟属性表是一致的,即Code属性表遵循属性表的结构,后面那些则是他自定义的结构。
④ InnerClasses 属性
为了方便说明特别定义一个表示类或接口的 Class 格式为 C。如果 C 的常量池中包含某个CONSTANT_Class_info 成员,且这个成员所表示的类或接口不属于任何一个包,那么 C 的ClassFile 结构的属性表中就必须含有对应的 InnerClasses 属性。InnerClasses 属性是在 JDK 1.1 中为了支持内部类和内部接口而引入的,位于 ClassFile结构的属性表。
⑤ LineNumberTable 属性
LineNumberTable 属性是可选变长属性,位于 Code结构的属性表。
LineNumberTable属性是用来描述Java源码行号与字节码行号之间的对应关系。这个属性可以用来在调试的时候定位代码执行的行数。
· start_pc,即字节码行号;line_number,即Java源代码行号。
在 Code 属性的属性表中,LineNumberTable 属性可以按照任意顺序出现,此外,多个 LineNumberTable属性可以共同表示一个行号在源文件中表示的内容,即 LineNumberTable 属性不需要与源文件的行一一对应。
LineNumberTable属性表结构:
⑥ LocalVariableTable 属性
LocalVariableTable 是可选变长属性,位于 Code属性的属性表中。它被调试器**用于确定方法在执行过程中局部变量的信息。**在 Code 属性的属性表中,LocalVariableTable 属性可以按照任意顺序出现。 Code 属性中的每个局部变量最多只能有一个 LocalVariableTable 属性。
· start pc + length表示这个变量在字节码中的生命周期起始和结束的偏移位置(this生命周期从头0到结尾)
· index就是这个变量在局部变量表中的槽位(槽位可复用)
· name就是变量名称
· Descriptor表示局部变量类型描述
LocalVariableTable 属性表结构:
⑦ Signature 属性
Signature 属性是可选的定长属性,位于 ClassFile, field_info
或 method_info结构的属性表中。在 Java 语言中,任何类、 接口、 初始化方法或成员的泛型签名如果包含了类型变量( Type Variables) 或参数化类型( Parameterized Types),则 Signature 属性会为它记录泛型签名信息。
⑧ SourceFile****属性
SourceFile属性结构
可以看到,其长度总是固定的8个字节。
⑨ 其他属性
Java虚拟机中预定义的属性有20多个,这里就不一一介绍了,通过上面几个属性的介绍,只要领会其精髓,其他属性的解读也是易如反掌。