“深入理解JVM:类文件结构”的版本间差异
(→常量池) |
|||
(未显示同一用户的1个中间版本) | |||
第100行: | 第100行: | ||
: [[File:常量池中的17种数据类型的结构总表3.jpg|600px]] | : [[File:常量池中的17种数据类型的结构总表3.jpg|600px]] | ||
=== | === 访问标志(access_flags)=== | ||
在常量池结束之后,紧接着的2个字节代表访问标志(access_flags),这个标志用于'''识别一些类或者接口层次的访问信息''',包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final;等等。 | |||
访问标志: | |||
:[[File:访问标志.jpg|600px]] | |||
* access_flags中一共有16个标志位可以使用,当前只定义了其中9个,没有使用到的标志位要求一律为零。 | |||
=== 类索引、父类索引与接口索引集合 === | === 类索引、父类索引与接口索引集合 === | ||
Class文件中由这三项数据来'''确定该类型的继承关系''': | |||
# 类索引(this_class):用于确定这个类的全限定名 | |||
#* u2类型的数据; | |||
# 父类索引(super_class):用于确定这个类的父类的全限定名 | |||
#* u2类型的数据:因为Java没有多重继承,有且仅有一个父类; | |||
# 接口索引集合(interfaces):用来描述这个类实现了哪些接口 | |||
#: 这些被实现的接口将按implements关键字(如果这个Class文件表示的是一个接口,则应当是extends关键字)后的接口顺序从左到右排列在接口索引集合中。 | |||
#* 一组u2类型的数据的集合: | |||
#* 对于接口索引集合,入口的第一项u2类型的数据为接口计数器(interfaces_count),表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。 | |||
=== 字段表集合 === | === 字段表集合 === |
2020年10月25日 (日) 01:47的最新版本
无关性的基石
代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。
- 无关性:即与平台无关,“一次编写,到处运行(Write Once,Run Anywhere)”;
- 无关性的基石:字节码(Byte Code):
- 各种不同平台的Java虚拟机,以及所有平台都统一支持的程序存储格式;
Java虚拟机不与包括Java语言在内的任何程序语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集、符号表以及若干其他辅助信息。
Class类文件的结构
任何一个Class文件都对应着唯一的一个类或接口的定义信息,但是反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以动态生成,直接送入类加载器中)。
- Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符。
根据《Java虚拟机规范》的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:
- “无符号数”:基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
- “表”:由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。
- 表用于描述有层次关系的复合结构的数据。
- 整个Class文件本质上也可以视作是一张表。
Class文件格式:
如上图,Class的没有任何分隔符,其数据项的顺序、数量,甚至于数据存储的字节序(Byte Ordering,Class文件中字节序为Big-Endian)等,都是被严格限定的,哪个字节代表什么含义,长度是多少,先后顺序如何,全部都不允许改变。
魔数、Class文件的版本
魔数(Magic Number):每个Class文件的头4个字节被称为魔数(Magic Number),唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。
- 很多文件格式标准中都有使用魔数来进行身份识别的习惯,因为相对于文件后缀更加安全;(譬如图片格式,如GIF或者JPEG等在文件头中都存有魔数)
- Class文件的魔数为:“0xCAFEBABE(咖啡宝贝?)”;
Class文件的版本号:紧接着魔数的4个字节存储的是Class文件的版本号;
- 第5和第6个字节是次版本号(MinorVersion)
- 第7和第8个字节是主版本号(Major Version)
- Java的版本号是从45开始;
- 高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件;
- 如:JDK 1.1能支持版本号为45.0~45.65535的Class文件,无法执行版本号为46.0以上的Class文件,而JDK 1.2则能支持45.0~46.65535的Class文件。
- 开头4个字节的十六进制表示是 0xCAFEBABE
- 代表次版本号的第5个和第6个字节值为 0x0000,而主版本号的值为 0x0032,也即是十进制的50,该版本号说明这个是可以被JDK 6或以上版本虚拟机执行的Class文件。
Class文件版本号:
常量池
紧接着主、次版本号之后的是常量池入口,常量池可以比喻为Class文件里的资源仓库,它是Class文件结构中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一,另外,它还是在Class文件中第一个出现的表类型数据项目。
- 由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count,从1而不是0开始的)。
常量池中主要存放两大类常量:
- 字面量(Literal):接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等
- 符号引用(Symbolic References):属于编译原理方面的概念,主要包括下面几类常量:
- 被模块导出或者开放的包(Package)
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
- 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
- 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
- 符号引用(如同之前内存部分所说):
- class文件中,方法调用其他方法,需要将其他方法的“符号引用”转化为直接引用,此过程即为“动态链接”;
- Java虚拟机栈的每个栈帧都有一个“指向运行时常量池中,该栈所属方法的”符号引用;
常量池中的17种数据类型的结构总表:
访问标志(access_flags)
在常量池结束之后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final;等等。
访问标志:
- access_flags中一共有16个标志位可以使用,当前只定义了其中9个,没有使用到的标志位要求一律为零。
类索引、父类索引与接口索引集合
Class文件中由这三项数据来确定该类型的继承关系:
- 类索引(this_class):用于确定这个类的全限定名
- u2类型的数据;
- 父类索引(super_class):用于确定这个类的父类的全限定名
- u2类型的数据:因为Java没有多重继承,有且仅有一个父类;
- 接口索引集合(interfaces):用来描述这个类实现了哪些接口
- 这些被实现的接口将按implements关键字(如果这个Class文件表示的是一个接口,则应当是extends关键字)后的接口顺序从左到右排列在接口索引集合中。
- 一组u2类型的数据的集合:
- 对于接口索引集合,入口的第一项u2类型的数据为接口计数器(interfaces_count),表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面接口的索引表不再占用任何字节。
字段表集合
方法表集合
属性表集合
字节码指令简介
6.4.1 字节码与数据类型 6.4.2 加载和存储指令 6.4.3 运算指令 6.4.4 类型转换指令 6.4.5 对象创建与访问指令 6.4.6 操作数栈管理指令 6.4.7 控制转移指令 6.4.8 方法调用和返回指令 6.4.9 异常处理指令 6.4.10 同步指令