深入理解 Java 虚拟机(结构篇)

Class 类文件的结构

概述

是一组以 8 位字节为基础单位的二进制流。

其格式是一种类似于 C 语言结构体的伪结构,由两种数据类型组成。

  1. 无符号数: 基本数据类型,数字、索引引用、数量值等等。
  2. 表: 由多个无符号数或者其他表组成,以 _info 结尾。

整个 Class 文件本质上就是一张表。

Class 结构的了解对进一步理解虚拟机执行引擎很重要。

魔数与 Class 文件版本

每个 Class 文件的头 4 个字节被称为魔数( Magic Number )。

作用是确定这个文件是否能被虚拟机接受,例如 Gif,jpeg 之类的文件也存在魔数。因为通过后缀来判断不够安全。

Class 文件的魔数值为: OxCAFEBABE (其魔数值某种程度上也是决定了 Java 未来的图标是咖啡 ^ v ^ )

第 5、6、7、8 个字节分别是次版本号和主版本号。

常量池

Class 文件的资源仓库。

常量池入口放置一个 u2 类型的数据,用于统计池中数量。

( u1,u2,u4,u8 分别代表 1、2、4、8 个字节的无符号数)

池中主要存放两种类型的常量:

  1. 字面量: 像 Java 中的 String,final 修饰的常量。
  2. 符号引用: 类、接口、字段、方法、名称描述。JVM

JDK 1.7 中有 14 中常量类型,每一项都是一个表。

访问标志

是否为 publicfinal 是类还是接口,是否抽象,是不是枚举等等。

类索引、父类索引、接口索引集合

字段表集合

用来描述类或接口中声明的变量,不包括局部变量。

字段作用域、是实例变量还是类变量、是否被 final 修饰等等。

方法表集合

描述方法,类似于字段的形式。

属性表集合

在字段表集合和属性表集合中都存在,用来描述某些场景的专有信息。

字节码指令

概述

JVM 的指令由一个字节长度的特定数字( Opcode )和数字后面带的参数( Operands )构成。

字节码与数据类型

在 Class 文件中对于不同的数据类型,采用不同的 Opcode 。

例如:

  • iload 用来操作 int 类型
  • fload 用来操作 float 类型

但是由于 JVM 中 Opcode 的长度是有限制的,只有 1 个字节,所以为每个数据类型都设计相应的 Opcode 是不现实的。

于是使用一些单独的指令,将不支持类型操作的,转换成支持的。

加载和存储指令

栈帧中的局部变量表和操作数栈之间数据的相互传输。

运算指令

也是针对不同的类型,有着不同的 Opcode,例如:

  • 加法: iaddladdfadddadd
  • 减法: isublsubfsubdsub
  • 按位与指令: iorlor

可以看出首字母就是数据类型。

类型转换指令

例如: i2b i2c i2s 等等

对象创建与访问指令

实例和数组采用不同的字节码指令。

操作数栈管理指令

例如:

  • 将操作数栈的栈顶一个或两个元素出栈: poppop2
  • 将栈最顶端的两个数值互换: swap

控制转移指令

可以让指令不按顺序执行程序,本质上来说就是修改 PC Register 的值。

Java 中的保留字 goto 就是无条件分支中的控制转移指令。

方法调用和返回指令

例如:

  • invokestatic 用于调用类方法
  • invokeinterface 用于调用接口方法

异常处理指令

JVM 中,处理 catch 语句不是由字节码指令来实现,是采用异常表来实现的。

同步指令

方法级的同步和方法内部的同步都是使用 Monitor 来支持的。

方法中调用过的每条 monitorenter 指令都必须执行对应的 monitorexit 指令。