代码编译的结果从本地机器码转变为字节码(Byte Code),是存储格式发展的一小步,却是编程语言发展的一大步。
存储格式发展:
今天的计算机仍然只能识别0和1,但将我们编写的程序编译成 二进制本地机器码(Native Code)已不再是唯一的选择,越来越多的程序语言选择了与 操作系统和机器指令集无关的、 平台中立的格式作为程序编译后的存储格式。
各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(ByteCode) 是构成平台无关性的基石,而且语言无关性正越来越被开发者所重视。
Java虚拟机不和包括Java在内的任何语言绑定,它 只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含 了 Java虚拟机指令集和符号表以及若干其他辅助信息。
Class类文件结构:
结构:
任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。文中只是通俗地将任意一个有效的类或接口所应当满足的格式称为“Class文件格式”,实际上它并不一定以磁盘文件的形式存在。
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎 全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项 时,则会按照高位在前[1]的方式分割成若干个8位字节进行存储。
Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型 :无符号数和表
无符号数 : 属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个 字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8 编码构成字符串值。
表 : 是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地 以“_info”结尾。
组成:
①魔数(Magic Number): 每个Class文件的头4个字节称为魔数,它的唯一作用是确定这个文件 是否为一个能被虚拟机接受的Class文件。
②版本号 : 第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。
③常量池 : 紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库, 它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。
常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
字面量 : 比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。
符号引用 : 则属于编译原理方面的概念,包括了下面三类常量:
1.类和接口的全限定名(Fully Qualified Name)
2.字段的名称和描述符(Descriptor)
3.方法的名称和描述符
④访问标志 : 在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识 别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类 型;是否定义为abstract类型;如果是类的话,是否被声明为final等。
⑤类索引、父类索引和接口索引集合 : 都按顺序排列在访问标志之后.
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,接口索引集合(interfaces)是一组u2类型的数据的集合.
类索引 : 用于确定这个类的全限定名
父类索引 : 用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java 类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。
接口索引集合 : 就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句
⑥字段表集合 : 用于描述接口或者类中声明的变量。
字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。我们可以想一想在Java中描述一个字 段可以包含什么信息?可以包括的信息有:字段的作用域(public、private、protected修饰 符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰 符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类 型、对象、数组)、字段名称。
字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代 码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类 实例的字段。另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是 否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致, 那字段重名就是合法的。
⑦方法表集合 : 用于对方法的描述,与对字段的描述几乎采用了完全一致的方式.
访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、
属性表集合(attributes): 方法里的Java代码,经过编译器编译成字节 码指令后,存放在方法属性表集合中一个名为“Code”的属性里面
⑧属性表集合 : 在Class文件、字段表、方 法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
字节码指令 :
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。
字节码指令集是一种具有鲜明特点、优劣势都很突出的指令集架构,由于限制了Java虚 拟机操作码的长度为一个字节(即0~255),这意味着指令集的操作码总数不可能超过256 条;又由于Class文件格式放弃了编译后代码的操作数长度对齐,这就意味着虚拟机处理那些超过一个字节数据的时候,不得不在运行时从字节中重建出具体数据的结构,如果要将一个 16位长度的无符号整数使用两个无符号字节存储起来(将它们命名为byte1和byte2).