JVM-栈帧

帧通常用来存放数据和部分结果,通常还有执行动态链接,方法返回值和异常调度。

在方法调用的同一时刻,一个新帧就会随之创建,当方法执行完成的时候(无论这个方法是正常完成还是意外完成),帧都会随之销毁。在线程创建帧的时候,Java虚拟机栈就会分配内存空间给帧。每个帧都有自己的局部变量表、操作数栈、当前方法对应类的运行时常量池的引用。

在编译时就确定了局部变量表的大小、操作数栈和与帧相关联的方法字节码。此外,帧的数据结构大小依赖于JVM的实现,并且在方法调用的同时分配这些数据结构所需要的内存空间。当只有一个方法正在执行并且只有一个帧的时候,可以根据线程调配随时激活。此时,这个帧称为当前帧,并且这个帧对应的方法称为当前方法,这个方法所在的类称为当前类。对局部变量表和操作数栈的操作通常都会引用当前帧。

如果方法调用其他方法或者方法执行完成时,方法的帧会随之销毁。当方法把控制权转移到新方法的时候,新方法就会执行,在执行的同时生成一个新的帧,新帧会切换成当前帧。对于方法返回,当前帧会返回帧对应方法的执行结果。更有可能,会返回到前一帧。当前一帧变成当前帧的时候,之前的当前帧就会销毁。

==注意:线程创建的帧是线程的私有空间,其他线程均不能引用==

局部变量表(local variables)

在前面提到了,每一帧包含的变量集称为它的局部变量表,一个帧的变量集的长度在编译时就确定了,并且类或接口的方法与之关联的帧也随之表现为二进制形式。

单个局部变量的值的类型可以是基本类型、引用或返回地址,一对局部变量的值的类型可以是long或double。

局部变量表是通过索引来寻址的,第一个局部变量的索引是0,所有索引都必须是0到局部变量表的长度减一之间(array.length-1).

如果值的类型是long或double,就会占用两位局部变量位,这样的值只能用较小的索引来处理.
例如,类型为double的值存储在局部变量表的索引是n,实际上它占用的局部变量表的位置是n和n+1两个位置,然而索引为n+1的局部变量是加载不了,如果n+1可以存储,那么n位置的变量就会变成无效。

JVM就不会再请求n位置的变量。很明显,类型为long和double的值在局部变量表中是不需要64位来对齐,JVM的实现使用两个局部变量位来存储这个值,这种方式比使用64位来存储更适合。

在方法调用的时候,JVM会使用局部变量表来传递参数。在类的方法执行的时候,所有参数都是通过以0开始的局部变量表来传递。在方法传递实例中,局部变量表中的第一位经常用来存储方法实例所对应的对象引用(在java中通常是指this)。局部变量表的第二位开始用来存储方法的参数。

操作数栈

在前面已经提到,每一帧都有后进先出的栈称为操作数栈,当前帧的最大栈深度在编译时就已经确定,并且与方法相关联的帧也已经表现为二进制形式。

在上下文比较明显的位置,我们有时会将当前帧的操作数栈作为简单的操作数栈来引用.

当帧刚创建时操作数栈是空的,JVM提供了加载常量、局部变量、字段到操作数栈的指令,其他JVM指令包含从操作数栈中获取操作数,在操作数上进行操作,以及把结果返回给操作数栈。操作数栈也可以用来准备传递给方法的参数和接收方法返回值。
例如:

iadd: iadd
Operation: Add int
Format: iadd
Forms: iadd = 96 (0x60)
Operand: ..., value1, value2 → 
Stack: ..., result

iadd方法的指令有两个int值,在前一条指令的作用下,方法请求的int值被添加到了操作数栈的最前两位,接着,所有的int值都从操作数栈中弹出,他们已经相加过,并且它们的总和也被推回到操作数栈中,这些计算操作也可以嵌套在操作数栈中,返回值可以包含在计算中.

操作数栈中的每个实体可以是JVM中任何类型的值,包括long类型或double类型。

操作数堆栈的值必须按其类型进行操作,但这不是绝对的。例如,要传入的是两个int值,现在却可以用两个long或两个float来传入,并把它们传入到isadd指令中来操作。有少量的JVM指令作用在运行时数据区的时候,被作为原始值来使用,这些指令不能用来修改或打断私有值。操作数栈上的这些操作规则都是通过class文件来验证。

在任何时刻,操作数栈都有其关联的深度,long类型或double类型占用了深度的两个单位,其他类型则占用了一个单位。

动态链接

在前面已经提到过,每个帧都包含有当前方法的运行时常量池的引用,这个引用用于支持方法字节码的动态链接。方法在执行的时候就会引用方法在class文件中的字节码,并且通过符号引用(symbolic references)来访问变量,动态链接会翻译符号方法引用以进入具体的方法引用,在必要时还要加载class来解析这些还没定义的符号,同时,翻译变量也是需要到存储结构中找到这些变量的相关运行时位置。

在其他类中的方法可以中断这些最后绑定的方法和变量.

普通方法执行完成

如果一个方法的执行没有引起异常,那么这个方法的执行就算正确完成。如果当前方法正常执行完成,那么就会返回一个值到当前正在执行的方法中,当执行方法的其中一个返回指令运行的时候就会返回结果,JVM会根据值的类型来选择返回类型。

当前帧通常用来恢复执行者的状态,包括恢复它的局部变量表和操作数栈,执行者的程序计数器会适当的添加跳过传递给方法执行的指令。当前正在执行的方法正常结束之后,方法的帧携带着操作结果重新返回到操作数栈中。

意外方法执行完成

如果在方法在执行过程中引起了异常导致JVM抛出异常或者抛出方法内没有处理的异常从而导致方法不能正常结束,这种方式称为意外方法执行完成。意外完成的方法执行永远不会返回值给调用处。

你可能感兴趣的:(JVM-优化)