JVMS8 - Java虚拟机的结构

class文件格式

由Java虚拟机执行的编译代码使用独立于硬件和操作系统的二进制格式表示,通常(但不一定)存储在文件中,称为类文件格式。类文件格式精确定义了类或接口的表示形式,包括可能以特定于平台的目标文件格式获取的字节顺序等详细信息。

数据类型

JVM有两种类型,primitive typereference type

primitive type

primitive type分为numeric types、boolean type 和returnAddress type。

numeric types

numeric type包含integral typesfloating-point types

intergral types: byte、short、int、long、char。

floating-point types: float、double。

boolean type

没有Java虚拟机指令专门用于boolean值的操作。操作boolean值得Java表达式被编译为使用JVM int数据类型的值。

JVM直接支持boolean数组。newarray指令允许创建一个boolean数组。boolean类型的数组使用byte数组指令baloadbastore访问和修改。

1
在Oracle JVM实现中,java编程语言中的boolean数组被编码为JVM byte数组,每个boolean元素使用8个字节。

JVM编码boolean数组使用1标识true、0标识false。

returnAddress类型

returnAddress类型被JVM中jsr,ret,jsr_w指令使用。returnAddress类型的值是指向Java虚拟机指令操作码的指针。returnAddress不对应任何Java编程语言类型,并且不能由运行程序修改。

reference type

有三种类型的reference types: class type,array type和interface type。

运行时数据区

pc寄存器(PC Register)

JVM支持多线程同时执行。每个JVM线程都有一个自己的pc(program counter) register。如果非本地方法(native),pc寄存器包含当前正在执行的Java虚拟机指令的地址。如果本地找方法,JVM pc寄存器的值是#未定义的。pc寄存器的值足够容纳returnAddress或特定平台的本地指针。

栈(Stack)

每个JVM线程都有一个私有的JVM栈,跟线程一起创建。JVM存储帧,它持有局部变量和部分结果,并在方法调用和返回中起作用。由于除了推送和弹出帧之外,永远不会直接操作Java虚拟机堆栈,因此帧可能是堆分配。JVM栈内存不必是连续的。

JVM栈可能出现的异常:

  • 如果线程中的计算需要比允许更大的JVM栈空间,JVM抛出StackOverflowError
  • 如果JVM栈可以动态的扩展,当内存不足时抛出OutOfMemoryError

堆(Heap)

JVM堆可以被所有JVM线程共享。堆是运行时数据区,从中分配所有类实例和数组的内存。

方法区(Method Area)

JVM方法区被所有JVM线程共享。它存储每类结构,例如运行时常量池,字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法。方法区在虚拟机启动时创建。

运行时常量池(Run-Time Constant Pool)

运行时常量池是类文件中constant_pool表的每类或每接口运行时表示。它包含几种常量,从编译时已知的数字字面量到必须在运行时解析的方法和字段引用。

每个运行时常量池都是从Java虚拟机的方法区域分配的。当Java虚拟机创建类或接口时,将构造类或接口的运行时常量池。

本地方法栈(Native Method Stacks)

JVM使用传统栈俗称C栈,以支持本地方法。

帧(Frame)

帧用于存储数据和部分结果,以及执行动态链接、方法的返回值和派发(dispatch)异常。

每次方法执行时创建一个新的帧。当方法执行完成时帧销毁,不管是正常完成或是抛出一个未捕获的异常。帧在线程的栈中创建。每一个帧都有它自己局部变量数组,操作数栈,对当前方法类的运行时常量池的引用。局部变量数组和操作数堆栈的大小在编译时确定。

调用方法时,会创建一个新帧,并在控制转移到新方法时变为当前帧。在方法返回时,当前帧将其方法调用的结果(如果有)传递回前一帧,然后被丢弃。

局部变量

每个帧包含一个称为局部变量的变量数组。帧的局部变量数组长度在编译期间确定。一个局部变量可以持有boolean,byte,char,short,int,float,reference或returnAddress的值。一对局部变量可以持有long和double值。

通过索引来解决局部变量。第一个局部变量的索引是0.

Java虚拟机使用局部变量在方法调用上传递参数。在类方法调用中,任何参数都在从局部变量0开始的连续局部变量中传递。在实例方法调用中,局部变量0始终用于传递对调用实例方法的对象的引用(Java编程语言中的this)。随后,任何参数都在从局部变量1开始的连续局部变量中传递。

操作数栈(Operand Stacks)

每个帧包含一个称为操作数栈的LIFO栈。操作数堆栈的最大深度在编译时确定。

当创建包含它的帧时,操作数堆栈为空。JVM提供指令以将局部变量或字段中的常量或值加载到操作数堆栈上。其他JVM指令从操作数堆栈获取操作数,对它们进行操作,并将结果推回操作数堆栈。操作数堆栈还用于准备要传递给方法和接收方法结果的参数。

动态链接(Dynamic Linking)

每个帧包含对运行时常量池的引用,该引用用于支持方法代码的动态链接的当前方法的类型。

坚持原创技术分享,更多深度分析、实践代码,您的支持将鼓励我继续创作!