访问标志
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 标志为public类型 |
ACC_FINAL | 0x0010 | 标志被声明为final,只有类可以设置 |
ACC_SUPER | 0x0020 | 标志允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真。(使用增强的方法调用父类方法) |
ACC_INTERFACE | 0x0200 | 标志这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假 |
ACC_SYNTHETIC | 0x1000 | 标志此类并非由用户代码产生(即:由编译器产生的类,没有源码对应) |
ACC_ANNOTATION | 0x2000 | 标志这是一个注解 |
ACC_ENUM | 0x4000 | 标志这是一个枚举 |
-
在常量池后,紧跟着访问标记
-
使用两个字节表示
-
用于识别一些类或者接口层次的访问信息:
- Class是类还是接口
- 是否定义为public类型
- 是否定义为abstract类型
- 如果是类,是否被声明为final等
-
类的访问权限通常为ACC_开头的常量
-
每一种类型的表示都是通过设置访问标记的32位中的特定位来实现的
-
若是public、final的类,则该标记为ACC_PUBLIC、ACC_FINAL。
-
使用ACC_SUPER可以让类更准确地定位到父类的方法super.method(),现代编译器都会设置并且使用这个标记。
补充
- 带有
ACC_INTERFACE
标志的class文件表示的是接口而不是类,反之则表示的是类而不是接口。 - 如果一个class文件被设置了
ACC_INTERFACE
标志,那么同时也得设置ACC_ABSTRACT
标志,同时它不能再设置ACC_FINAL
、ACC_SUPER
或ACC_ENUM
标志。
如果没有设置ACC_INTERFACE
标志,那么这个class文件可以具有上表中除ACC_ANNOTATION
外的其他所有标志。当然,ACC_FINAL
和ACC_ABSTRACT
这类互斥的标志除外。这两个标志不得同时设置。 ACC_SUPER
标志用于确定类或接口里面的invokespecial指令使用的是哪一种执行语义。针对Java虚拟机指令集的编译器都应当设置这个标志- Java SE 8及后续版本来说,无论class文件中这个标志的实际值是什么,也不管class文件的版本号是多少,Java虚拟机都认为每个class文件均设置了
ACC_SUPER
标志。 ACC_SUPER
标志是为了向后兼容由旧Java编译器所编译的代码而设计的,目前的ACC_SUPER
标志在由JDK1.0.2之前的编译器所生成的access_flags中是没有确定含义的,如果设置了该标志,那么Oracle的Java虚拟机实现会将其忽略ACC_SYNTHETIC
标志意味着该类或接口是由编译器生成的,而不是由源代码生成的- 注解类型必须设置
ACC_ANNOTATION
标志。如果设置了ACC_ANNOTATION
标志,那么也必须设置ACC_INTERFACE
标志 ACC_ENUM
标志表明该类或其父类为枚举类型
类索引、父类索引、接口索引集合
长度 | 含义 |
---|---|
u2 | this_class |
u2 | super_class |
u2 | interfaces_count |
u2 | interfaces[interfaces_count] |
- 三项数据来确定这个类的继承关系
- 类索引用于确定这个类的全限定名
- 父类索引用于确定这个类的父类的全限定名,Java语言不允许多重继承,所以父类索引只有一个
- 除了java.lang.Object之外,所有的Java类都有父类,因此除Ijava.lang.Object外,所有Java类的父类索引都不为0
- 接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(这个类是一个接口则是extends语句)后的接口顺序排列在接口索引集合中
this_class
- 2字节无符号整数,指向常量池的索引,提供了类的全限定名,如java/lang/Object,
this_class
的值必须是对常量池表中某项的一个有效索引值 - 常量池在这个索引处的成员必须为
CONSTANT_Class_info
类型结构体,该结构体表示这个class文件所定义的类或接口
super_class
- 2字节无符号整数,指向常量池的索引,提供了当前类的父类的全限定名,没有继承任何类,其默认继承的是java/lang/object类。
super_class
指向的父类不能是final
interfaces
-
interfaces_count
:接口计数器 -
指向常量池索引集合,它提供了一个符号引用到所有已实现的接口
-
一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的
CONSTANT_Class
(当然这里必须是接口,不是类) -
interfaces[]中每个成员的值必须是对常量池表中某项的有效索引值,它的长度为interfaces_count
-
每个成员interfaces[i]必须为CONSTANT_Class_info结构
-
在interfaces[]中,各成员所表示的接口顺序和对应的源代码中给定的接口顺序
字段表集合
-
描述接口或类中声明的变量,字段(field)包括类级变量、实例级变量,不包括方法内部、代码块内部声明的局部变量
-
字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
-
指向常量池索引集合,它描述了每个字段的完整信息,比如字段的标识符、访问修饰符(public、private或protected)
-
是类变量还是实例变量(static修饰符)
-
是否是常量(final修饰符)等
-
fields_count
:字段计数器 -
fields_count的值表示当前class文件fields表的成员个数。使用两个字节来表示
注意事项
- 字段表集合中不会列出从父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段
- 在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。
字段
标志名称 | 标志值 | 含义 | 数量 |
---|---|---|---|
u2 | access_flags | 访问标志 | 1 |
u2 | name_index | 字段名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 属性计数器 | 1 |
attribute_info | attributes | 属性集合 | attributes_count |
- fields表中的每个成员都必须是一个fields_ info结构的数据项,用于表示当前类或接口中某个字段的完整描述
访问标识
一个字段的信息包括如下这些信息。这些信息中,各个修饰符是布尔值
标志名称 | 标志值 | 含义 |
---|---|---|
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_TRANSTENT | 0x0080 | 字段是否为transient(序列化) |
ACC_SYNCHETIC | 0x1000 | 字段是否为由编译器自动产生 |
ACC_ENUM | 0x4000 | 字段是否为enum |
描述符
- 用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte,char,double,float,int,long,short,boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示
属性
- 一个字段还可能拥有一些属性,用于存储更多的额外信息,比如初始化值、一些注释信息等
- 属性个数存放在attribute_count中,属性具体内容存放在attributes数组中
- 对于常量属性而言,attribute_length值恒为2。
方法表集合
-
methods
:指向常量池索引集合,它完整描述了每个方法的签名 -
字节码文件中,每一个method_info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public、private或protected),方法的返回值类型以及方法的参数信息等
-
如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来
-
methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法
-
methods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息,比如:类(接口)初始化方法<clinit>()和实例初始化方法<init>())。
注意事项
-
Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。
-
但在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个class文件中。
-
尽管Java语法规范并不允许在一个类或者接口中声明多个方法签名相同的方法,但是和Java语法规范相反,字节码文件中却恰恰允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。
-
methods_count
:方法计数器,两个字节来表示 -
methods_count的值表示当前class文件methods表的成员个数
-
methods表中每个成员都是一个method_info结构
-
methods表中的每个成员都必须是一个
method_info
结构,用于表示当前类或接口中某个方法的完整描述 -
如果某个
method_info
结构的access_flags
项既没有设置ACC_NATIVE
标志也没有设置ACC_ABSTRACT
标志,那么该结构中也应包含实现这个方法所用的Java虚拟机指令。 -
method_info结构可以表示类和接口中定义的所有方法,包括
- 实例方法
- 类方法
- 实例初始化方法
- 类或接口初始化方法
方法表结构
标志名称 | 标志值 | 含义 | 数量 |
---|---|---|---|
u2 | access_flags | 访问标志 | 1 |
u2 | name_index | 方法名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 属性计数器 | 1 |
attribute_info | attributes | 属性集合 | attributes_count |
访问标志
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | public,方法可以从包外访问 |
ACC_PRIVATE | 0x0002 | private,方法只能本类访问 |
ACC_PROTECTED | 0x0004 | protected,方法在自身和子类可以访问 |
ACC_STATIC | 0x0008 | static,静态方法 |
属性表集合
-
方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该class文件的源文件的名称。以及任何带有
RetentionPolicy.CLASS
或者RetentionPolicy.RUNTIME
的注解 -
这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试
-
字段表、方法表都可以有自己的属性表,用于描述某些场景专有的信息
-
属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性
-
attributes_count
:当前class文件属性表的成员个数 -
属性表中每一项都是一个attribute_info结构
属性
- 属性表的每个项的值必须是attribute_info结构
- 属性表的结构比较灵活,各种不同的属性只要满足以下结构即可
通用格式
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u1 | info | attribute_length | 属性表 |
属性类型
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类,方法,字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
InnerClass | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
StackMapTable | Code属性 | JDK1.6中新增的属性,供新的类型检查检验器和处理目标方法的局部变量和操作数有所需要的类是否匹配 |
Signature | 类,方法表,字段表 | 用于支持泛型情况下的方法签名 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | 用于存储额外的调试信息 |
Synthetic | 类,方法表,字段表 | 标志方法或字段为编译器自动生成的 |
LocalVariableTypeTable | 类 | 是哟很难过特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 |
RuntimeVisibleAnnotations | 类,方法表,字段表 | 为动态注解提供支持 |
RuntimeInvisibleAnnotations | 类,方法表,字段表 | 用于指明哪些注解是运行时不可见的 |
RuntimeVisibleParameterAnnotation | 方法表 | 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象或方法 |
RuntimeInvisibleParameterAnnotation | 方法表 | 作用与RuntimeInvisibleAnnotations属性类似,只不过作用对象或方法 |
AnnotationDefault | 方法表 | 用于记录注解类元素的默认值 |
BootstrapMethods | 类文件 | 用于保存invokeddynamic指令引用的引导方法限定符 |
Code
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | max_stack | 1 | 操作数栈深度的最大值 |
u2 | max_locals | 1 | 局部变量表所需的存续空间 |
u4 | code_length | 1 | 字节码指令的长度 |
u1 | code | code_lenth | 存储字节码指令 |
u2 | exception_table_length | 1 | 异常表长度 |
exception_info | exception_table | exception_length | 异常表 |
u2 | attributes_count | 1 | 属性集合计数器 |
attribute_info | attributes | attributes_count | 属性集合 |
LineNumberTable
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{
u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
-
LineNumberTable属性是可选变长属性,位于Code结构的属性表
-
来描述Java源码行号与字节码行号之间的对应关系。这个属性可以用来在调试的时候定位代码执行的行数
-
start_pc:即字节码行号
-
line_number:Java源代码行号
-
在Code属性的属性表中,LineNumberTable属性可以按照任意顺序出现,
-
多个LineNumberTable属性可以共同表示一个行号在源文件中表示的内容,即LineNumberTable属性不需要与源文件的行一一对应
LocalVariableTable
LocalVariableTable_attribute{
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{
u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}
-
可选变长属性,位于Code属性的属性表中。它被调试器用于确定方法在执行过程中局部变量的信息
-
在Code属性的属性表中,LocalVariableTable属性可以按照任意顺序出现,Code属性中的每个局部变量最多只能有一个LocalVariableTable属性
-
start pc + length表示这个变量在字节码中的生命周期起始和结束的偏移位置(this生命周期从头到结尾,0~10)
-
index:这个变量在局部变量表中的槽位(槽位可复用)
-
name:变量名
-
descriptor:局部变量类型描述
文件结构总结
- 随着Java平台的不断发展,在将来,Class文件的内容也一定会做进一步的扩充,但是其基本的格式和结构不会做重大调整
- 从Java虚拟机的角度看,通过Class文件可以让更多的计算机语言支持Java虚拟机平台,因此,Class文件结构不仅仅是Java虚拟机的执行入口,更是Java生态圈的基础和核心