6、类文件结构

时间:2021-09-23 12:26:40

6.1、概述

写的程序需要经编译器翻译成由0和1构成的二进制格式才能由计算机执行

 

6.2、无关性基石

Java在刚刚诞生之时曾经提出过一个非常著名的宣传口号:“一次编写,到处运行(Write Once,Run Anywhere)”

充分表达了软件开发人员对冲破平台界限的渴求。

 

Sun公司以及其他虚拟机提供商发布了许多可以运行在各
种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现
了程序的“一次编写,到处运行”。

 

各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(ByteCode)是构成平台无关性的基石

 

实现语言无关性的基础仍然是虚拟机字节码存储格式。Java虚拟机不和包括Java在内
的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含
了Java虚拟机指令集符号表以及若干其他辅助信息。

如图:

6、类文件结构

 

Java语言中的各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成
的,因此字节码命令所能提供的语义描述能力肯定会比Java语言本身更加强大。因此,有一
些Java语言本身无法有效支持的语言特性不代表字节码本身无法有效支持,这也为其他语言
实现一些有别于Java的语言特性提供了基础。

 

6.3、Class类文件的结构

注意:

任何一个Class文件都对应着唯一一个类或者接口的定义信息

反过来说:类或者接口并不一定都得定义再文件里

 

Class文件是一组以8位字节为基础的二进制流,各个数据项严格按照顺序紧凑的排列再Class文件之中

中间没有添加任何分隔符,使得整个Class文件中的存储内容几乎全部是运行的必要数据,没有空隙存在

当遇到需要占用8位字节以上的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储

 

Class文件格式采用一种类似于C语言结构体的伪结构体来存储数据

这种伪结构中只有两种数据类型:无符号数和表,解析都要经过这两种数据类型为基础

 

无符号数属于基本数据类型,以u1、u2、u4、u8来分别代表一个字节,2个字节,4个字节,8个字节的无符号数

无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值

 

表是由多个无符号数或者其他表作为数据项构成的符合数据类型,所有表都习惯性的以“_info"结尾

 

Class文件格式:

6、类文件结构

无论是无符号数还是表,当需要描述同一个类型但数量不定的多个数据,经常会使用一个前置的容量计数器

加若干个连续的数据项形式,这时称这一系列连续的某一项数据为某一个类型的集合

 

6.3.1、魔数于Class文件的版本

每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用就是确定这个文件是否为一个能被虚拟机接受的Class文件

很多文件存储标准中都使用魔数来进行身份识别,如图片格式、gif过着jpeg等再文件头中都存在魔数

使用魔数而不是扩展名来进行识别主要是基于安全性方面的考虑,因为文件扩展名可以随意改动

文件格式的制定者可以*的选择魔数值,只要这个魔数值还没有被广泛采用过的同时又不会引起混淆即可

魔数值在Java还称做“Oak”语言的时候(大约是1991年前后)就已经确定下来了。

 

紧接着魔数的4个字节存储的是Class文件的版本号:

第5、第6个字节是次版本号

第7、第8个字节是主版本号

Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1

高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后此版本的Class文件

即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件

 

 新建一个Class文件:

6、类文件结构

 

 使用WinHex打开这个Class文件:

6、类文件结构

开头4个字节:0x7061636B

代表次版本号:0x6167

主版本号:0x6520

 

JDK1.1到JDK1.7主流JDK版本编译输出的默认和可支持的Class文件版本号:

6、类文件结构

这种顺序称为”Big-Endian“,具体是指最高位字节再地址的最低位

最低位字节再地址最高位的顺序来存储数据

它是SPARC、PowerPC等处理器的默认多字节存储顺序。

 

6.3.2、常量池

接着主次版本号之后的是常量池入口,常量池可以理解位Class文件之中的资源仓库

它是Class文件结构中与其他项目关联最多的数据类型

也是占用Class问价空间最大的数据项目之一,同时还是再Class文件中第一个出现的类型数据项目

 

由于常量池中常量数目不是固定的,所以常量池入口需要放置一项u2类型的数据

代表常量池容量计数值

 

与Java中语言习惯不一样的是:这个容器计数是从1开始的

上图所示的常量池容量为0x6F72

 

在Class文件格式规范制定之时,设计者将第0项常量空出来是有特殊考虑的,这样做的目的在
于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池
项目”的含义,这种情况就可以把索引值置为0来表示。

 

Class文件结构中只有常量池的容量计
数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量
计数都与一般习惯相同,是从0开始的

 

常量池中主要存放两大类常量字面量(Literal)和符号引用(Symbolic References)。

字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等

符号引用则属于编译原理方面的概念,包括了下面三类常量:
1、类和接口的全限定名(Fully Qualified Name)
2、字段的名称和描述符(Descriptor)
3、方法的名称和描述符

 

Java代码再进行javac编译的时候,不像C、C++那样由“连接”这一个步骤

而是再虚拟机中加载class文件的时候进行动态连接

 

Class文件中不会保存各个方法、字段的最终内存布局信息

因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址

也就无法直接被虚拟机使用

当虚拟机运行时,需要从常量池获得对应的符号引用

在类创建时或者运行时解析、翻译到具体的内存地址之中

 

常量池中每一项常量都是一个表,在JDK 1.7之前共有11中结构各不相同的表结构数据

在JDK1.7为了更好的支持动态语言调用,又增加了三种:

(CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info)

 

这14种都有一个共同特点

从表的第一位是一个u1类型的标志位(tag)代表当前常量属于那种常量类型

常量池的项目类型:

6、类文件结构