方法调用指令

在JDK7之前方法调用的字节码指令共有四条,invokeinterface、invokespecial、invokestatic、invokevirtual。由这四条指令完成Java中所有类型方法的调用。

invokeinterface(调用接口方法)

无符号数indexbyte1和indexbyte2共同组件一个当前类常量池索引(index),该索引值为(indexbyte1<<8)|indexbyte2,即高位在前的两个字节无符号值。在常量池中索引为index的数据项必须为一个接口方法的符号引用,该符号引用指明了该接口方法的简单名称和描述符,并且指明了该接口方法所在接口。然后该方法将被解析,解析出来的方法不能是实例初始化方法“<init>()”,也不能是接口或类初始化方法“<clinit>()”。

操作数count是一个不为0的无符号数。objectref必须是一引用类型变量,在操作栈中,objectref后面必须跟随若干个参数值,这些参数值的数量,类型和顺序必须与解析出的接口方法描述符一致。第四个操作数的值必须为0。

假设objectref是类C引用变量,那么真正被调用的方法将按如下顺序进行查找:

a.如果类C声明了一个与接口方法的简单名称与描述符都相同的实例方法,那么这个实例方法被调用,查找程序结束。

b.否则,如果类C有父类,那么按照继承关系从下往上依次对C的各个父类进行第a步的搜索。

c.否则,虚拟机抛出一个AbstractMethodError错误。

如果被调用的方法含有synchronized修饰符,objectref的监视器会随着当前线程中monitorenter指令的执行而进入或重进行。如果被调用的方法不是本地方法,nargs参数值与objectref将一起从操作数栈中弹出,在Java虚拟机栈中将为该方法调用建立一个新的栈帧。objectref与参数值连续地放入新建栈帧的局部变量表中,objectref进入slot0,arg1进入slot1(如果arg1是long或者double类型的话,将占用slot1和slot2),依此类推。这时新建的栈帧成为当前栈帧,Java虚拟机的pc指向被调用方法的第一条字节码指令地址,紧接着就将执行方法的第一条指令。

如果被调用的方法是本地方法(native),如果这时实现该方法的具体平台的代码还没有绑定到Java虚拟机,那么就要去加载具体平台的代码进虚拟机。nargs参数值和objectref将从操作数栈中被弹出,并作为实现该本地方法代码的参数被传入,然后具体平台代码将以具体平台方式被调用。当平台代码执行完成返回时:

a.如果该本地方法含有synchronized修饰符,那么objectref关联的监视器在当前线程执行monitorexit指令后值被更新,如果监视器值变回0则退出。

b.如果该本地方法有返回值,那么该返回值在进行了具体实现相关的转换后被压入操作数栈。

连接异常:

在接口方法的符号引用解析过程中,任何有关接口方法解析的异常都可能被抛出。

运行时异常:

否则,如果objectref值为null,那么invokeinterface指令将抛出一个NullPointerException。

否则,如果objectref所在类没有实现被解析的接口,那么invokeinterface指令抛出IncompatibleClassChangeError错误。

否则,如果没有与简单名称和描述符都匹配的方法,那么invokeinterface将抛出AbstractMethodError。

否则,如果被选择的方法不是public的,invokeinterface将抛出IllegalAccessError错误。

否则,如果被选择的方法是abstract的,invokeinterface将抛出AbstractMethodError错误。

否则,如果被选择的方法是native的并且其本地实现方法没找到,nvokeinterface将抛出UnsatisfiedLinkError错误。

invokeinterface指令的count操作数记录了被选择方法的参数值数量,如果参数类型是long或double,那么该参数将按两个单位进行计算,其它任何参数类型按一个单位进行计算。第4个操作数在Oracle的Java虚拟机实现中使用,用于在运行时使用专门的伪指令替换掉invokeinterface指令,但它必须保证向前兼容。

invokespecial(调用私有方法,父类方法,类实例构造器)

无符号数indexbyte1和indexbyte2共同组件一个当前类常量池索引(index),该索引值为(indexbyte1<<8)|indexbyte2,即高位在前的两个字节无符号值。在常量池中索引为index的数据项必须为一个方法的符号引用,该符号引用指明了该方法的简单名称和描述符,并且指明了该方法所在类。如果该方法解析出来后发现在protected的,它是当前类父类的一个成员,并且该没有在当前类的同一个run-time
package声明,那么objectref所属类必须是当前类或者当前类的子类。

接下来,被解析的方法要能被选中执行,必须满足以下所有条件:

a.当前类的访问标记中设置了ACC_SUPER标记(为1)

b.被解析方法所属类是当前类的父类

c.被解析方法不是实例初始化方法“<init>”(虽然实例初始化方法是由invokespecial指令调用,但其在当前类的初始化阶段就已经被执行完成,被初始化过的类是不能再执行类初始化方法的)

如果以上条件都成立,那么真正被执行的方法将按如下顺序行进选择。假设C是当前类的直接父类:

1.如果类C声明了一个与被解析方法简单名称与描述符都相同的实例方法,那么该被将被调用,查找程序结束。

2.否则,如果类C还有父类,那么按照继承关系从下往上依次对C的父类进行第1步的搜索。

3.否则,虚拟机抛出一个AbstractMethodError错误。

其它行为,如建立新的栈帧,参数入局部变量表,解析异常,运行时异常等与invokeinterface指令类似。

invokestatic(调用类、静态方法)

无符号数indexbyte1和indexbyte2共同组件一个当前类常量池索引(index),该索引值为(indexbyte1<<8)|indexbyte2,即高位在前的两个字节无符号值。在常量池中索引为index的数据项必须为一个方法的符号引用,该符号引用指明了该方法的简单名称和描述符,并且指明了该方法所在类。被解析的方法不能是实例初始化方法“<init>()”,也不能是类或接口初始化方法“<clinit>()”,并且它必须是static的,所以它不能是abstract的。当方法成功被解析,声明该方法的类的初始化工作也完成了(如果还未初始化的话,触发类的初始化阶段就包含了invokestatic指令)。该指令的操作数与其它三个指令相比,最大的区别是没有了objectref,因为静态方法没有接收者。

其它行为,如建立新的栈帧,参数入局部变量表,解析异常,运行时异常等与invokeinterface指令类似。

invokevitual(调用虚方法)

参看方法调用中关于虚方法调用步骤相关内容

时间: 2024-08-09 10:42:41

方法调用指令的相关文章

Jvm(48),指令集----方法调用和指令返回

方法调用(分派.执行过程)将在第8章具体讲解,这里仅列举以下5条用于方法调用的指令. invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式. invokeinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用. invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法.私有方法和父类方法. invokestatic指令用于调用类方法

多态方法调用的解析和分派

方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.在程序运行时,进行方法调用是最普遍.最频繁的操作,Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于之前说的直接引用).这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法调用过程变得相对复杂起来,需要在类加载期间,甚至到运行期间才能确定目标方法

深入理解Java虚拟机笔记---方法调用

方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.在程序运行时,进行方法调用是最普遍.最频繁的操作.在Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(相当于直接引用).这个特性给Java带来了更强大的动态扩展能力,但也使得Java方法的调用过程变得相对复杂,需要在类加载期间甚至到运行期间才能确定目标方法的直接引用.

JVM理论:(三/4)方法调用

本文主要总结虚拟机调用方法的过程是怎样的,JAVA虚拟机里面提供了5条方法调用的字节码指令.分别如下: invokestatic:调用静态方法 invokespecial:调用实例构造器<init>方法.私有方法和父类方法. invokevirtual:调用所有的虚方法. invokeinterface:调用接口方法,会在运行时期再确定一个实现此接口的对象. invokedynamic:现在运行时期动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条指令,分派逻辑都是固化在虚拟

04 JVM是如何执行方法调用的(下)

虚方法调用 Java 里所有非私有实例方法调用都会被编译成 invokevirtual 指令,而接口方法调用会被编译成 invokeinterface 指令.这两种指令,均属于 Java 虚拟机中的虚方法调用. 动态绑定:Java 虚拟机需要根据调用者的动态类型,来确定虚方法调用的目标方法. 静态绑定:调用静态方法的 invokestatic 指令,以及用于调用构造器,私有实例方法和超类非私有实例方法的 invokestatic 指令.如果虚方法调用指向一个标记为 final 的方法,那么 Ja

JVM是如何执行方法调用的?(下)

前言....... abstract class 乘客 { abstract void 出境 (); @Override public String toString() { ... } } class 外国人 extends 乘客 { @Override void 出境 () { /* 进外国人通道 */ } } class 中国人 extends 乘客 { @Override void 出境 () { /* 进中国人通道 */ } void 买买买 () { /* 逛免税店 */ } } 乘

关于JVM中方法调用的相关指令,以及解析(Resolution)和分派(Dispatch)的解释——重载的实现原理与重写的实现原理

JVM中相关方法的调用的指令 invokestatic 调用静态方法. invokespecial 用于调用构造器方法<init>.私有方法.父类方法. invokevirtual 用于调用类的所有虚方法. invokeinterface 用于调用接口方法. 解析(resolution)与分派(dispatch) 解析 解析调用一定是个静态的过程,在编译期间就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变 为可确定的直接引用,不会延迟到运行期再去完成. 下面我们看一段代码: /**

JVM方法调用

当我们站在JVM实现的角度去看方法调用的时候,我们自然会想到一种分类: 1.编译代码的时候就知道是哪个方法,永远不会产生歧义,例如静态方法,private方法,构造方法,super方法. 2.运行时才能确定是哪个方法,这也正是多态的实现原理. 对于第一种方法的调用,有2个字节码指令:invokestatic,invokespecial invokestatic:调用static方法(不需要通过类的实例就可以调用),这很好理解.静态方法属于整个类型,就一份,没有歧义. invokespecial:

JVM方法调用(invokevirtual)

在java代码运行期间,方法间的调用可以说是最为频繁的了,那么这些方法间的调用在底层的虚拟机又做了什么事情呢?现在就让我们揭开那道神秘的面纱. JVM调用方法有五条指令,分别是invokestatic,invokespecial,invokevirtual,invokeinterface,invokedynamic.invokestatic用来调用静态方法:invokespecial用来调用私有方法,父类方法(super.),类构造器方法:invokeinterface调用接口方法:invoke