无关性的基石
各种不同平台的虚拟机与所有平台都统一使用的程序存储格式—字节码(ByteCode)是构成平台无关性的基石。
无关性:
- 平台无关性
- 语言无关性
实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class”这种特定的二进制文件格式所关联。
Class类文件的结构
- 任何一个Class文件都对应着唯一一个类或是接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里。
- Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑第排列在Class文件中,中间没有任何分隔符。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储(大端模式)。
- Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种结构中只有两种数据类型:无符号数和表。
- 无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串。
- 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。整个Class文件本质上就是一张表。
魔数与Class文件的版本
- 每个Class文件的头4个字节称为魔数,唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件,使用魔数不是扩展名来进行识别主要是基于安全方面的考虑,值为:0xCAFEBABE。
- 紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号,第7和第8个字节是主版本号。
常量池
紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时他还是在Class文件中第一个出现的表类型数据项目。
常量池容量计数器是从1开始计数的,表示常量的个数。
常量翅中主要存放两大类常量:
- 字面量
- 符号引用
字面量比较接近于Java语言层面的常量概念,如字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面的三类常量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
常量池中每一个常量都是一个表。 - 常量池容量为21,表示有21项常量
由于Class文件中方法、字段等都需要引用CONSTANT_Utf8_info型常量来描述名称,所以CONSTANT_Utf8_info型常量的最大长度也就是Java中方法、字段名的最大长度。而这里的最大长度就是length的最大值,既u2类型能表达的最大值65535.所以Java程序中如果定义了超过65KB因为字符串的变量或方法名将会无法编译。
javap工具用于分析Class文件字节码的。
访问标志
这个标志用于识别一些类或者是接口层次的访问信息。
类索引、父类索引与接口索引集合
Class文件中由这三项数据来确定这个类的继承关系。
字段表集合
字段表用于描述接口库或者类中声明的变量。
字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
字段表集合中不会列出从超类或者父类接口中继承而来的字段,但有可能列出原本Java代码中不存在的字段。对于字节码来说,如果两个字段的描述符不一致,那字段重名就是合法的。
方发表集合
volatile关键字和transient关键字不能修饰方法,所以方发表的访问标志中没有了ACC_VOLATITE标志和ACC_TRANSIENT标志。
属性表集合
在Class文件、字段表、方发表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
操作栈数?
max_stack代表了操作数栈深度的最大值。在方法执行的任意时刻,操作栈数都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度。
1 Code属性
了解Code属性是学习后面关于字节码执行引擎内容的必要基础,能直接阅读字节码也是工作中分析Java代码语义问题的必要工具和基本技能。
使用Javap 命令计算字节码指令: javap -verbose TestClass
在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留第一个Slot位来存放对象实例的引用,方法参数值从1开始计算。,这个处理只对实例方法有效。
2 Exceptions属性
Exceptions属性的作用是列举出方法中可能抛出的受检查异常(Checked Exceptions),也就是方法描述时在throws关键字后面列举的异常。
3 LineNumberTable属性
LineNumberTable属性用于描述Java源码行号与字节码行号之间的对应关系。
4 LocalVariableTable属性
LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系
5 SourceFile属性
SourceFile属性用于记录生成这个Class文件的源码文件名称。
6 ConstantValue属性
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。onstantValue的属性值只能限于基本类型和String。
7 InnerClasses属性
InnerClasses属性用于记录内部类与宿主类之间的关联。
8 Deprecated及Synthetic属性
9 StackMapTable属性
10 Signature
11 BootstrapMethods属性
字节码指令简介
Java虚拟机的指令由一个字节长度、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。
1 字节码与数据结构
阅读字节码作为了解Java虚拟机的基础技能,是一项应当熟练掌握的能力。
2 加载和存储指令
加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
3 运算指令
运算指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。
算术指令分为两种:
- 对整型数据进行运算的指令。
- 对浮点型数据进行运算的指令。
无论哪种算术指令,都使用Java虚拟机的数据类型,由于没有直接支持byte,short,char和boolean类型的算术指令,对于这类数据的运算,应使用操作int类型的指令代替。
4 类型转换指令
5 对象创建与访问指令
6 操作数栈管理指令
操作数栈的指令:
- 将操作数栈的栈顶一个或两个元素出栈:pop、pop2。
- 复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup_x2、dup2_x2。
- 将栈最顶端的两个数值互换:swap。