class 文件结构
- class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在 class 文件之中,中间没有添加任何分隔符,整个 class 文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。
- 当遇到 8 位字节以上的空间的数据项时,则会按照高位在前的方式分割成若干个 8 位字节进行存储。
- class 文件中有两种数据类型,分别是无符号数和表(引用类型)。
- 魔数
- class 文件版本
- 常量池
- 访问标志
- 类索引,父类索引,接口索引集合
- 字段表集合
- 方法表集合
- 属性表集合
魔数
- 版本
- JDK 1.8=52
- JDK 1.7=51
- JDK 1.6=50
- JDK 1.5=49
- JDK 1.4=48
- JDK 1.3=47
- JDK 1.2=46
- JDK 1.1=45
class 文件以 cafebabe 开头,后面跟着的是 JDK 次版本号和主版本号
常量池
CP_info
CONSTANT_Methodref_info 用于记录方法信息(包括类中定义的方法以及代码中使用到的方法)
查看 class 文件信息:javap -verbose HelloWorld.class
访问标志
类索引
字段表集合
字段表用于描述接口或者类中声明的变量
方法表集合
属性表集合
字节码指令
- Java 虚拟机的指令由一个字节长度的,代表着某种特定操作含义的数字,称之为操作码,以及跟随其后的零至多个代表此操作所需参数的操作数而构成。
- 操作码长度为 1 个字节,因此最大只有 256 条。
- 基于栈的指令集架构
字节码与数据类型
- 在虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息。
- lload fload
- 大多数指令是包含类型信息的
- 也有不包含类型信息的
- goto 与类型无关
- Arraylength 操作数组类型
- 类型多,指令少
加载和存储指令
- 加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。
- 将局部变量表加载到操作数栈:iload lload fload dload aload
- 将一个数值从操作数栈存储到局部变量表:istore lfda
- 将一个常量加载到操作数栈:bipush sipush ldc ldc_w_ldc2_w aconst_null iconst_m1 iconst
- 扩充局部变量表的访问索引的指令:wide
运算指令
- 运算或算术指令用于对两个操作数栈上的值进行某种特定的运算,并把结果存储到操作数栈顶。
- 加法指令:add
- 减法指令:sub
- 乘法指令:mul
- 除法指令:div
- 取余指令:rem
- 取反指令:neg
类型转换指令
- 类型转换指令可以将两种不同的数值类型进行相互转换,这些转换操作一般用于实现用户代码中的显示类型转换操作以及用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。
- 宽化类型处理和窄化类型处理
- i2b i2c i2s l2i
|
|
|
|
对象创建与访问指令
- 创建类实例的指令:new
- 创建数组的指令:newarray anewarray multianewarray
- 访问类字段:getfield putfield getstatic putstatic
- 把数组元素加载到操作数栈的指令:baload c s i l f d a
- 将操作数栈的值存储到数组元素:astore
- 取数组长度的指令:arraylength
- 检查实例类型的指令:instanceof checkcast
操作数栈管理指令
- 操作数栈指令用于直接操作操作数栈
- 将操作数栈的一个或两个元素出栈:pop pop2
- 复制栈顶一个或两个数值并将复制或双份复制值重新压入栈顶:dup dup2 dup_x1 dup_x2
- 将栈顶的两个数值替换:swap
控制转移指令
- 控制转移指令可以让 Java 虚拟机有条件或无条件的从指定的位置指令而不是控制转移指令的下一条指令继续执行程序。可以认为控制转移指令就是在修改 pc 寄存器的值。
- 条件分支:ifeq iflt ifle ifne ifgt ifnull ifcmple
- 复合条件分支:tableswitch lookupswitch
- 无条件分支:goto goto_w jsr jsr_w ret
方法调用和返回指令
方法调用
- invokevirtual 指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是 Java 语言中最常见的方法分派方式。
- invokeinterface 指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
- invokespecial 指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
- invokestatic 指令用于调用类方法(static 方法)
方法的返回指令
- 方法的调用指令与数据类型无关,而方法返回指令则是根据返回值的类型区分的,包括 ireturn(当返回值是 boolean、byte、char、short 和 int 类型时使用)、lreturn、freturn、dreturn 和 areturn,另外还有一条 return 指令供声明为 void 的方法、实例初始化方法、类和接口的类初始化方法使用。
异常处理指令
在程序中显式抛出异常的操作会由 athrow 指令实现,除了这种情况,还有别的异常会在其他 Java 虚拟机指令检测到异常状况时由虚拟机自动抛出。
新 jdk 不再使用字节码指令执行 catch 语句了,而使用 exception table 执行 catch 下语句。
Java虚拟机是如何处理异常的?
How the Java virtual machine handles exceptions
同步指令
同步指令
- Java 虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(monitor)来支持的。
- 方法级的同步是隐式的,即无需通过字节码指令来控制,它实现在方法调用和返回操作中。虚拟机可以从方法常量池中的方法表结构(method info structure)中的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有管程,然后再执行方法,最后在方法完成(无论正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获得同一个管程。如果一个同步方法执行期间抛出了异常,并在方法内部无法处理此异常,那这个同步方法所持有的管程将在异常抛到同步方法外时自动释放。
- 同步一段指令集序列通常是由 Java 语言中的 synchronized 块表示,Java 虚拟机的指令集中有 monitorenter 和 monitorexit 两条指令来支持 synchronized 关键字的语义,正确实现 synchronized 关键字需要编译器与 Java 虚拟机两者协作支持。
管程
- 管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。
- 管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。
- 系统中的各种硬件资源和软件资源,均可用数据结构抽象地描述其资源特性,即用少量信息和对资源所执行的操作来表征该资源,而忽略了它们的内部结构和实现细节。
- 利用共享数据结构抽象地表示系统中的共享资源,而把对该共享数据结构实施的操作定义为一组过程。