深入理解JAVA虚拟机之类文件结构
类文件结构
java规范将*java语言规范*和*java虚拟机规范*拆分,可见设计者在设计之初就考虑了让其他语言运行在JVM虚拟机之上,就目前而言以
及出现大量运行在java虚拟机之上的语言比如我们熟悉的Groovy,Scala,Jython.不过这些只是在语言层面上有不通之处,最终都需要编译成
JVM支持的.class文件才能最终被java虚拟机加载和运行,虚拟机并不关心Class的来源是何种语言,因此了解Class文件是理解JAVA虚拟机的
基本条件。
Class 类文件结构
- Class文件是一组以8位字节为基础单位的二进制流,中间没有任何分隔符
- Class文件格式采用一种类似C语言结构体的伪结构存储数据,伪结构中只有两种数据类型(无符号数和表)
- 无符号数属于基本数据类型以u1,u2,u4,u8来表示1个字节,2个字节,4个字节和8个字节的无符号数,无符号数可以描述数字,索引,数量值,或者按照UTF-8编码的字符串值
- 表是由多个无符号数或者其他表作为数据项构成的复合数据结构,一般习惯性都已_info结尾
Class文件格式-摘自《深入理解java虚拟机》
类型 | 名称 | 数量 |
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_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
接下来对class文件中每项做深入解释
magic 魔数与class版本
Class文件开头四个字节为魔数字,作用是确定这是一个合法的class文件,和拓展名类似,不过拓展名可以被用户篡改。
紧接着魔数的4个字节存储的是class的版本号,5,6字节是次版本号 minor version,7,8字节是主版本号 major version。JVM可以向下兼容,但是不能接受比自己版本高的class字节码。JAVA版本号从45开始,之后每个JDK大版本向上加1。
常量池(cp_info)
常量池是Class中的资源仓库,它是class文件结构中和其他项目关联最多的数据结构。也是占用class文件空间最大的项目之一。由于常量池的中常量数目不确定,所以需要在常量池入口放置一项u2 (constant_pool_count)代表常量池中常量计数。常量池中主要存放2大类常量字面量和符号引用
字面量:类似于java语言层面的常量,如文本字符串,声明为final的常量值等
符号引用:1.类和接口的全限定名,2.字段的名称和描述符,3.方法的名称和描述符
常量池中每一个常量都是一个表,常量池中项目类型如下:引用自《深入理解JAVA虚拟机》
常量池中数据项类型 | 类型标志 | 类型描述 |
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 | 对一个字段或方法的部分符号引用 |
这14个表都有一个共同特点。表开始第一位是一个u1类型的标示位(tag),代表当前这个常量属于哪种类型常量
访问标示(access_flags)
这个标示用来识别一些类或者接口层次的访问信息,包括这个Class是类还是接口,是否为public 是否为abstract,是否是final等。
标示位具体含义如下表
标志名
|
标志值
|
标志含义
|
针对的对像
|
ACC_PUBLIC
|
0x0001
|
public类型
|
所有类型
|
ACC_FINAL
|
0x0010
|
final类型
|
类
|
ACC_SUPER
|
0x0020
|
使用新的invokespecial语义
|
类和接口
|
ACC_INTERFACE
|
0x0200
|
接口类型
|
接口
|
ACC_ABSTRACT
|
0x0400
|
抽象类型
|
类和接口
|
ACC_SYNTHETIC
|
0x1000
|
该类不由用户代码生成
|
所有类型
|
ACC_ANNOTATION
|
0x2000
|
注解类型
|
注解
|
ACC_ENUM
|
0x4000
|
枚举类型
|
枚举
|
类索引,父类索引和接口集合
类索引(this_class),父类索引(super_class),接口集合(interfaces),其中类索引和父类索引都是u2类型,的索引值,只想一个类型为CONSTANT_class_info的常量,根据该常量又可以找到在CONSTANT_Utf8_info中的全局限定名的字符串值。对于接口集合入口第一项为u2计数器(由于java可以实现多接口,但是只能单继承,所以接口数目是不定的),而接口集合是一个u2数据类型的集合,标示接口的在常量池中CONSTANT_class_info索引值
字段表集合
字段表(field_info)用于描述接口或者类中声明的变量。字段包括了类变量和实例变量,不包括在方法内声明的局部变量。
描述一个字段主要包括 作用域,static修饰符,final修饰符,可见性 volite,是否序列化 transient,字段数据类型。因此很适合使用标示位来表示。而字段的名称和数据类型无法固定,这些只能引用常量池来表示。
字段访问标示如下
标志名称 | 标志值 | 含义 |
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_VOLATILE | 0x00 40 | 字段是否为volatile |
ACC_TRANSTENT | 0x00 80 | 字段是否为transient |
ACC_SYNCHETIC | 0x10 00 | 字段是否为由编译器自动产生 |
ACC_ENUM | 0x40 00 | 字段是否为enum |
字段描述符标示和含义
标志符 | 含义 |
B | 基本数据类型byte |
C | 基本数据类型char |
D | 基本数据类型double |
F | 基本数据类型float |
I | 基本数据类型int |
J | 基本数据类型long |
S | 基本数据类型short |
Z | 基本数据类型boolean |
V | 基本数据类型void |
L | 对象类型 |
对于数组每一个维度使用一个前置的[ 来表示,比如string数组用 [Ljava/lang/Stirng。
用描述符来描述方法时,按照先参数列表,后返回值的顺序描述。参数列表安装参数的严格顺序放在一组小括号内。
比如void inc()的描述符为 ()V
方法表集合
Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式,方法表结构同字段表一样包含了访问标示符,名称索引,描述符索引,属性表索引。
由于方法的特殊性(方法内包含有用户的业务代码)。因此上述的结构只是标示了方法的定义信息,方法内的JAVA代码,经过编译器编译成字节码指令后,存放在方法属性表集合中的一个名为Code的属性里面,属性表是Class文件格式中最具有拓展性的一种数据结构。
属性表集合
在Class文件中,字段表,方法表都可以携带自己的属性集合表,用于描述某些场景专有的信息。
虚拟机规范预定义的部分属性如下:
属性名称 | 使用位置 | 含义 |
Code | 方法名 | JAVA代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类,方法表,字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
SourceFile | 类文件 | 源文件名称 |
Synthetic | 类,方法表,字段表 | 标识方法或字段为编译器自动生成的 |