Java虚拟机规范规定Class文件格式统一采用一种类似于C语言结构体的伪结构体来存储数据,
这种伪结构体存储两种数据类型:无符号数和表。
无符号数:属于基本的数据类型,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照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_flag | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interface_count | 1 |
u2 | interfaces | interface_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | method_count | 1 |
method_info | methods | methods_count |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
魔数
每个Class文件的头四个字节称为魔数,其值的16进制表示为0xCAFEBABE(换算为二进制为1100 1010 1111 1110 1011 1010 1011 1110),虚拟机在加载类时分析该文件是否为Class文件。
版本号
第5、6个字节表示次版本号。第7、8个字节表示主版本号。
高版本的JDK能够向下兼容低版本的Class文件,虚拟机会拒绝执行超过其版本的Class文件。
常量池
常量池中存放两大类常量:
- 字面量常量:
比较接近Java语言层面的常量概念,比如字符串和被声明为final的常量值。对于范围在-127~128之间的包装类型也是常量。 - 符号引用常量:属于编译原理方面的概念,包含
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
常量池的大小不固定,用两个字节表示,第零项空出来,为了满足某些指向常量池的索引值的数据“不引用任何一个常量池项目”。
常量池的每一项常量都是一个表,这个表的第一位都是一个u1类型的标识位。
下面是常量池中的14种项目类型:
下面是常量池中的14种项目类型的结构表:
常量池中数据项类型 | 类型标志 | 类型描述 |
---|---|---|
CONSTANT_Utf8 | 1 | UTF-8编码的Unicode字符串 |
CONSTANT_Integer | 3 | int类型字面值 |
CONSTANT_Float | 4 | float类型字面值 |
CONSTANT_Long | 5 | long类型字面值 |
CONSTANT_Double | 6 | double类型字面值 |
CONSTANT_Class | 7 | 对一个类或接口的符号引用 |
CONSTANT_String | 8 | String类型字面值 |
CONSTANT_Fieldref | 9 | 对一个字段的符号引用 |
CONSTANT_Methodref | 10 | 对一个类中声明的方法的符号引用 |
CONSTANT_InterfaceMethodref | 11 | 对一个接口中声明的方法的符号引用 |
CONSTANT_NameAndType | 12 | 对一个字段或方法的部分符号引用 |
访问标志
访问标志标示了当前类的修饰符,比如 public / final / super / interface / enum / abstract / annotation / synthetic
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 是否为Public类型 |
ACC_FINAL | 0x00 10 | 是否被声明为final,只有类可以设置 |
ACC_SUPER | 0x00 20 | 是否允许使用invokespecial字节码指令的新语义. |
ACC_INTERFACE | 0x02 00 | 标志这是一个接口 |
ACC_ABSTRACT | 0x04 00 | 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假 |
ACC_SYNTHETIC | 0x10 00 | 标志这个类并非由用户代码产生 |
ACC_ANNOTATION | 0x20 00 | 标志这是一个注解 |
ACC_ENUM | 0x40 00 | 标志这是一个枚举 |
类索引、父类索引、接口索引集合
- 类索引:this_class, 用于确定这个类的全限定名
- 父类索引:super_class, 父类索引用于确定这个类的父类的全限定名
- 接口索引集合:interfaces, 用于描述这个类实现了哪些接口
字段表集合
字段表集合(field_info)用于描述接口或者类中声明的变量,包含类变量和实例变量,但不包含方法内部声明的局部变量。
|类型|名称|数量|
|—-|—-|—-|—-|
|u2 |access_flag|1|访问修饰符|
|u2 |name_index | 1| 字段的简单名称,如String str = “123”, str就是简单名称|
|u2 |descritpor_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 |
ACC_FINAL | 0x0010 | 字段是否为final |
ACC_VOLATILE | 0x0040 | 字段是否为volatile |
ACC_TRANSTENT | 0x0080 | 字段是否为transient |
ACC_SYNCHETIC | 0x1000 | 字段是否为由编译器自动产生 |
ACC_ENUM | 0x4000 | 字段是否为enum |
描述符:
描述符的作用是用来描述字段的数据类型、方法的参数列表(数量、类型、顺序)和返回值。
根据描述符的规则:基本数据类型以及代表无返回值的void类型都用一个大写的字符来表示;
而对象类型则用字符L加对象的全限定名来描述;
标志符号 | 含义 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void 按照先参数列表后返回值的顺序描述,参数列表按照顺序放在”()”内部,String toString()描述为V()java.lang.String |
L | 对象类型 |
[ | 数组类型 [I: 代表int[], [[I:代表int[][] |
方法表集合
Class 文件中对方法的描述和对字段的描述完全是一致的, 方法表中的结构和字段表中的接口一样。
因为volatile关键字和transient关键字不能修饰方法,所以方法表的访问标志中没有ACC_VOLATILE和ACC_TRANSIENT。 但是增加了synchronized,native,abstract,strictfp(strict float point, 精确的浮点数)关键字的修饰符。
对于方法里的代码,经过编译器变异常字节码指令后,存放在方法属性表中一个名为code的属性里。
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 方法是否为public |
ACC_PRIVATE | 0x00 02 | 方法是否为private |
ACC_PROTECTED | 0x00 04 | 方法是否为protected |
ACC_STATIC | 0x00 08 | 方法是否为static |
ACC_FINAL | 0x00 10 | 方法是否为final |
ACC_SYHCHRONRIZED | 0x00 20 | 方法是否为synchronized |
ACC_BRIDGE | 0x00 40 | 方法是否是有编译器产生的方法 |
ACC_VARARGS | 0x00 80 | 方法是否接受参数 |
ACC_NATIVE | 0x01 00 | 方法是否为native |
ACC_ABSTRACT | 0x04 00 | 方法是否为abstract |
ACC_STRICTFP | 0x08 00 | 方法是否为strictfp (strict-float-point,精准的浮点数) |
ACC_SYNTHETIC | 0x10 00 | 方法是否是有编译器自动产生的 |
属性表集合
在Class文件、字段表、方法表中都可以携带自己的属性表集合(attribute_info),用于描述某些场景的专有信息。
不强制要求各属性表的顺序,只要不与已有属性表重名即可。
任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机在运行时会忽略不认识的属性。
属性名称 | 使用位置 | 含义 |
---|---|---|
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指令引用的引导方式限定符 |
对于看了文章还有点懵的同学,看这里的视频讲解,忽略广告。《全网最牛JVM字节码结构分析、Class类文件核心结构》。