方法调用指令

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

invokeinterface(调用接口方法)

方法调用指令_第1张图片

   无符号数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(调用私有方法,父类方法,类实例构造器)

方法调用指令_第2张图片

   无符号数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(调用类、静态方法)

方法调用指令_第3张图片

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

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

invokevitual(调用虚方法)

方法调用指令_第4张图片

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

你可能感兴趣的:(jvm,invokevirtual,invokeinterface,invokespecial,invokestatic)