类文件结构
一、平台无关性的基础
- 各种不同平台的虚拟机与所有平台统一使用字节码(
ByteCode
)作为程序的存储格式,是构成平台无关性的基础。 - 实现语言无关性的基础也是虚拟机和字节码存储格式。
二、Class
类文件结构
(注: 任何一个Class
文件都对应唯一一个类或者接口的定义信息,但类和接口并不一定都跟都定义在文件里,如可以通过类加载器直接生成。)
(1) Class
文件简介
Class
文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的安排在Class
文件之中,中间无任何分隔符。当遇到需要占用8位字节以上空间的数据项时,按照高位在前的方式分割成若干个8位字节进行存储。Class
文件数据类型: 无符号数和表
1) 无符号数
u1、u2、u4、u8
分别代表1个字节、2个字节、4个字节、8个字节。无符号数描述数字、索引引用、数字量或者按照UTF-8
编码构成的字符串值。
2) 表
由多个无符号数或者其他表作为数据项构成的复合数据类型。以_info
结尾。Class
文件格式
(2) Class
文件详解
1. 魔数(Magic Number
)
用以确定这个文件是否是一个能被虚拟机接受的Class
文件。(4个字节0xCAFEBABE
)
2. 次版本号和主版本号
第5、6字节存储的是次版本号(Minor Version
),第7、8字节存储的是主版本号(Major Version
)。高版本的JDK
能向下兼容以前版本的Class
文件,但不能运行以后版本的Class
文件,即使文件格式并未发生变化,虚拟机也必须拒绝执行超过其版本好的Class
文件。
3. 常量池
1)常量池是
Class
文件中的资源仓库,是与其他项目关联最多的数据类型,也是占用Class
文件空间最大的数据项目之一,还是Class
文件中第一个出现表数据类型的数据项目。2)在常量池入口处放置了一项
u2
类型的数据constant_pool_count
,代表常量池容量计数值。该容量计数器从1而不是从0开始,如容量为0x0016
,即十进制的22
,这代表常量池中常量有21
个,索引范围为1~21
。Class
文件结构中只有常量池的容量计数是从1开始的,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都是从0开始。3)常量池存放类型(常量都是表结构)
- 字面量(字符串、final
常量)
- 符号引用(类和接口的全限定名、字段的名称与描述符、方法的名称与描述符)4) 常量池常量类型(表结构)
表开始的第一位是一个u1
类型的标志位(tag
),代表当前这个常量属于哪种常量类型。可以使用javap命令输出常量表
, 如javap -verbose TestClass
4. 访问标志(Access_flags, U2)
5. 类索引、父类索引、接口索引
1) 类索引this_class(U2)
确定这个类的全限定名,父类索引super_class(U2)
确定这个类的父类的全限定名。
2) 接口索引描述这个类实现的接口类型,按照顺序排列在接口索引集合中。
6. 字段表集合
字段表用于描述接口或者类中声明的变量,字段包括类级变量以及实例级变量,但不包括在方法内声明的局部变量。
u2 fields_count; // 有多少个字段
field_info {
u2 access_flags; // 例如是public , private 等等,字段访问标志
u2 name_index; // 指向常量池的入口,字段的简单名称
u2 descriptor_index; //指向常量池的入口,字段以及方法的描述符
u2 attributes_count; // 该字段的属性有多少个,ConstantValue
attribute_info attributes[attributes_count]; //属性信息
}
字段的描述符:用来描述字段的数据类型、方法的参数列表(包括数量、顺序、以及类型)和返回值。
1) 基本数据类型(byte/char/double/float/int/long/short/boolean
)以及代表无返回值的void
类型采用一个大写字母表示;对象类型用字符L
加对象的全限定名表示,如Ljava/lang/Object
。
2) 对于数组,java.lang.String[][]
—> [[Ljava/lang/String
; int[]
—> [I
3) 描述符描述方法时,按照先参数表,后返回值的顺序描述,参数列表严格按照参数顺序放在一个小括号内()
。如void inc()
描述符为()V
; java.lang.String.toString()
描述符为()Ljava/lang/String;
; (复杂) int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)
的描述符为([CII[CIII)I
7. 方法表集合
u2 methods_count; // 有多少个方法
method_info {
u2 access_flags; //访问标志
u2 name_index; //名称索引
u2 descriptor_index; //描述符
u2 attributes_count; //属性表集合,包括Code属性
attribute_info attributes[attributes_count];
}
8. 属性表集合
在Class
文件、字段表、方法表可以携带自己的属性表集合,用于描述某些场景专有的信息,不要求各个属性表具有严格的顺序。
(1) Code
属性
Code
属性表结构如下:
Code_attribute {
u2 attribute_name_index; //指向常量池,应该是UTF8Info ,值为"Code"
u4 attribute_length; //属性长度, 不包括开始的6个字节
u2 max_stack; // 操作数栈的最大深度(注:编译时已经确定)
u2 max_locals; // 最大局部变量表个数(第一个Slot存放this对象引用)
u4 code_length; // 该方法的代码长度
u1 code[code_length]; //真正的字节码
u2 exception_table_length;
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
(2) Exception
属性
列举出方法中可能抛出的受检查异常Checked Exception
,即throws
关键字后面列举的常量。
(3) LineNumberTable
属性
用于描述Java
源码行号与字节码偏移量的对应关系。javac
可以使用-g:none
或者-g:lines
选项来取消或者要求生成这项信息。
(4) LocalVariableTable
属性
用于描述栈帧中局部变量表中的变量与Java
源码中定义的变量之间的关系,也不是运行时比必须的属性。-g:none
或者-g:vars
选项可以取消或者要求生成这项信息。
(5) SourceFile
属性
记录生成的这个Class
文件的源码文件名称。
(6) ContantValue
属性(通知虚拟机自动为静态变量赋值)
如果某字段为静态类型( access_flags 中包含 ACC_STATIC 标志),
将会被分配 ConstantValue 属性
(7) InnerClass.....
(3) 字节码指令(最多256个)
Java
虚拟机的指令由一个字节长度的、代表某种特定操作含义的数字(操作码)以及跟随其后的零至多个代表此操作所需参数(操作数)构成。
大部分的指令中没有支持整数类型
byte\char\short\boolean
,编译器会在编译期或者运行期将byte
、short
类型的数据带符号扩展为相应的int
类型,将boolean
和char
零位扩展为相应的int
类型。加载和存储指令
用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,如iload
、istore
等。运算指令
用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作数栈作栈顶。如iadd/isub/dcmpg
。类型转换指令
对象创建与访问指令
1) 创建类实例: new
2) 创建数组: newarray、anewarray、multianewarray
3) 访问类字段和实例字段: getfield、putfield、getstatic、putfield操作数栈管理指令
出栈: pop、pop2
入栈: dup、dup2…
交换: swap控制转移指令,有条件或者无条件的修改
PC
寄存器的值,如ifeq
。方法调用和返回指令
异常处理指令
同步指令