三 类文件结构
1 Java虚拟机的两种中立特性
· 平台无关性
· 语言无关性
实现平台无关性和语言无关性的基础是虚拟机和字节码存储格式(Class文件)。
2 Class类文件的结构
---Class文件是一组以8位字节为基础单位的二进制流,当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
---“高位在前”:即最高位字节在地址最低位、最低位字节在地址最高位。
---Class文件格式的两种数据类型:
· 无符号数:是基本的数据类型,包括u1、u2、u4、u8,可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成的字符串值。
· 表:是由多个无符号数或者其他表作为数据项构成的复合数据类型,用于描述有层次关系的复合结构的数据。
(1)魔数和Class文件的版本
· 魔数:
---每个Class文件的头4个字节。
---用于确定这个文件是否为一个能被虚拟机接受的Class文件。
---Class文件的魔数值为:0xCAFEBABE。
· 版本号:
---紧接着魔数的4个字节。
---第5和第6个字节是次版本号,第7和第8个字节是主版本号。Java的版本号是从45开始的。
(2)常量池
---常量池入口:u2类型,代表常量池容量计数值,计数从1开始而不是0开始(目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”时可以把索引值置为0)。
---常量池主要存放:字面量和符号引用。
---字面量:比较接近于java语言层面的常量概念,如文本字符串、声明为final的常量值。
---符号引用:属于编译原理方面的概念。包括:
· 类和接口的全限定名;
· 字段的名称和描述符;
· 方法的名称和描述符。
---常量池中每一项常量都是一个表,JDK1.7中,共有14种常量类型,见下表。
---具体细节略。
(3)访问标志
---常量池结束之后的两个字节代表访问标志,用于识别一些类或者接口层次的访问信息。
---一共有16个标志位可以使用,当前只定义了8个,未使用到的标志位要求一律为0。
(4)类索引、父类索引与接口索引集合
---类索引:一个u2类型数据,用于确定这个类的全限定名,指向一个类型为CONSTANT_Class_info的类描述符常量。
---父类索引:一个u2类型数据,用于确定这个类的父类的全限定名,指向一个类型为CONSTANT_Class_info的类描述符常量。
---接口索引:一组u2类型数据的集合,用于确定这个类实现了哪些接口,入口的第一项是接口计数器。
(5)字段表集合
---字段表用于描述接口或者类中声明的变量。字段包括类变量和实例变量,但不包括方法内部声明的局部变量。字段表结构如下表:
---其中,access_flags为字段修饰符,可以设置的标志位和含义见下表:
---name_index代表字段的简单名称,descriptor_index代表字段的描述符。
· 全限定名:把类的完全限定名中的”.“替换成了”/“。
· 简单名称:没有类型和参数修饰的方法或者字段名称。
· 描述符:描述字段的数据类型、方法的参数列表(包括数量、类型及顺序)和返回值。描述符标识字符含义如下表:
---对于数组类型,每一维度将使用一个前置的”[“字符来描述;
---用描述符描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号之内。
---字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,例如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
(6)方法表集合
---类似字段表的内容。
---方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为”Code“的属性里面。
(7)属性表集合
略
3 字节码指令简介
---Java虚拟机的指令:由一个字节长度的、代表着某种特定操作含义的数字(即操作码)以及跟随其后的零至多个代表此操作所需参数而构成。
---由于Java虚拟机操作码的长度为一个字节,意味着字节码指令集的操作码总数不超过256条。
(1)加载和存储指令
---用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。包括
· 将一个局部变量加载到操作栈,如iload;
· 将一个数值从操作数栈存储到局部变量表,如istore;
· 将一个常量加载到操作数栈,如bipush;
· 扩充局部变量表的访问索引的指令:wide。
(2)运算指令
---用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。包括:
· 加法指令,如iadd;
· 减法指令,如isub;
· 乘法指令,如imul;
· 除法指令,如idiv;
· 求余指令,如irem;
· 取反指令,如ineg;
· 位移指令,如ishl;
· 按位或指令,如ior;
· 按位与指令,如iand;
· 按位异或指令,如ixor;
· 局部变量自增指令,如iinc;
· 比较指令,如dcmpg;
(3)类型转换指令
---可以将两种不同的数值类型进行相互转换,可以用于
· 实现用户代码中的显示类型转换操作;
· 处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。
---Java虚拟机直接支持以下数据类型的宽化类型转换:
· int类型到long、float、或者double类型;
· long类型到float、double类型;
· float类型到double类型。
---int或long类型窄化转换为整数类型T的时候,丢弃除最低位N个字节以外的内容,N是类型T的数据类型长度。
---将一个浮点值窄化转换为整数类型T(int或long)的时候,遵循以下规则:
· 若浮点值为NaN,转换结果为T类型的0;
· 若浮点值不是无穷大的话,使用向零舍入模式取整,获得整数v,如果v在目标类型T的表示范围之内,那转换结果就是v;
· 否则,根据v的符号,转换为T所能表示的最大或者最小整数。
---将double类型窄化转换为float类型:采用最接近数舍入模式得到一个可以使用float类型表示的数字,
· 如果转换结果的绝对值太小而无法使用float来表示,将返回float类型的正负零;
· 如果转换结果的绝对值太大而无法使用float来表示,将返回float类型的正负无穷大;
· 对于double类型的NaN将按规定转换为float类型的NaN值。
---数据类型窄化转换可能发生上限溢出、下限溢出和精度丢失等,但永远不可能导致虚拟机抛出运行时异常。
(4)对象创建和访问指令
---包括:
· 创建类实例的指令:new;
· 创建数组的指令:newarray、anewarray、multianewarray;
· 访问类字段和实例字段的指令:getstatic、putstatic、getfield、putfield;
· 把一个数组元素加载到操作数栈的指令,如baload;
· 把一个操作数栈的值存储到数组元素中的指令,如bastore;
· 取数组长度的指令:arraylength;
· 检查类实例类型的指令:instanceof、checkcast。
(5)操作数栈管理指令
---包括:
· 将操作数栈的栈顶一个或者两个元素出栈:pop、pop2;
· 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶,如dup;
· 将栈最顶端的两个数值互换:swap。
(6)控制转移指令
(7)方法调用和返回指令
(8)异常处理指令
(9)同步指令