JVM系列(三)——方法的调用

来源:互联网 发布:外星人论坛源码 编辑:程序博客网 时间:2024/05/20 13:12


         Java代码在执行的时候,分为解释执行(通过解释器)和编译执行(通过编译器)

         一、栈帧

栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,在每次进入一个方法的时候,都会生成该方法的栈帧,并入栈。当方法执行完时,则将对应的栈帧出栈。每个方法对应一个栈帧,每次只有栈顶的栈帧有效,这样,各个方法的调用、内部变量的使用便不会相互干扰。

         在一个栈帧中,包含了局部变量表、操作数栈、动态链接、方法返回地址等各个部分的作用和数据结构。

1、 局部变量表。

局部变量表,顾名思义,用于存储某个方法内部的变量和方法参数。变量表中,以变量槽(Slot)作为基本单位,来存储各个变量。能够存储基本类型和reference、returnAddress类型。

2、 操作数栈

局部变量表用于存储数据,而对于数据的操作(算数运算),则需要使用操作数栈,通过入栈出栈进行。例如进行加操作时,将已经存入栈的最顶层的2个操作数取出,进行求和,并将结果存入栈。

3、 动态链接

主要是为了支持运行时才进行的关联的引用,例如支持多态性,而指向运行时常量池的引用。

4、 方法返回地址

用于在方法执行完毕或者遇到异常需要返回时,给出外部函数的PC地址。并存储一些恢复外部函数继续执行时的必要信息。

二、方法调用

方法调用与方法执行不同,方法调用的唯一任务是确定调用方法的版本。因为在class文件中存储的只是符号引用,而不是方法在实际运行时候的入口地址,故需要在类加载期间甚至到运行期间才能够确定目标方法的直接引用。

要确定方法的实际入口地址,有两种情况:

1、 编译期间能够确定。主要有静态方法和私有方法两大类。前者与类型直接关联,后者不可被访问,二者都无法重写出其他版本,故在类加载的时候便可进行解析。

2、 编译期间不能够确定,只能在运行期间确定。由于方法可能会被 “重写”,故无法在编译期间确定。

在多态的情况下,例如

Human man = new Man();

Human women = new Women();

局部变量表中的Reference类型的man,指向堆中生成的Man对象,而womon则指堆中的Women对象,在执行的时候,则会根据实际的指向进行调用了。这种,在运行阶段确定的分派动作成为动态分派。

但“重载”的函数是在编译期间确定的,他属于静态分派。在编译阶段,编译期会确定一个“最合适”的方法(需要类型转换次数最少…等一些规则),来进行入口函数地址的确定。“重载”方法并没有像“重写”的方法一样,有这样一个引用类型以供确定,他的方法的不同是在同一个类(重载)而非不同类中(重写),故无法实现动态分配。