JVM学习之:浅谈方法调用
提到方法调用,我想大多数人的第一反应就是执行一个方法呗,其实在虚拟机的眼里方法调用只是确定他要调用哪个方法而已,和方法的执行还是有比较大的区别的.任何一个层序的运行都离不开方法的调用以及方法的执行,但是在 JVM学习之:虚拟机中的运行时栈帧总结(二)提到过,在Class文件的编译过程中不包括传统的连接步骤(连接:把符号引用转化为可以直接找到方法体的直接引用),但是正是因为这点也给java带来了更大的灵活性,因为不同的实现可能会在不同的阶段对符号引用进行转化,下面是对几种常见的方法调用类型进行描述
解析:
在 JVM学习之:虚拟机中的运行时栈帧总结中提到了,如果符号引用是在类加载阶段或者第一次使用的时候转化为直接应用,那么这种转换成为 静态解析,那么什么样的方法会在这个阶段进行转换了?虚拟机规范提到,只有在真正运行之前就可以确定调用版本的,并且在运行时是不可变的方法,简单的讲,也就是不能被覆盖.不能被改变,不能被重载的方法,其实也就是static方法,private方法,final方法,<init>构造器,父类方法,这几种方法都是适合在类加载解析阶段讲符号引用转换为直接引用的,对这写种类方法的调用也被称为 解析.除了这些方法的其他方法也被称作为 虚方法()
前面提到了几种方法的调用,虚拟机也提供了对应的字节码指令,分别是:
invokestatic:调用静态方法
invokespecial:调用构造器方法,私有方法以及父类方法
invokevirtual:调用虚方法以及final方法(虽然用invokevirtual调用,可是因为final方法的不可覆盖性,因此也是非虚方法)
invokeinterface:调用接口方法,会在运行时再确定一个具体的实现方法
下面通过一个演示类的反编译结果来对上面的字节码指令进行验证:
package com.eric.jvm.executor; /** * 通过反编译字节码来验证 * invokestatic:调用静态方法 * invokespecial:调用构造器方法,私有方法以及父类方法 * invokevirtual:调用虚方法以及final方法(虽然用invokevirtual调用,可是因为final方法的不可覆盖性,因此也是非虚方法) * invokeinterface:调用接口方法,会在运行时再确定一个具体的实现方法 * * * javap -verbose com.eric.jvm.executor.InvokeCommandExecutor * * @author Eric * */ public class InvokeCommandExecutor { public static void main(String[] args) { //invokestatic SubInvoker.invokeStatic(); //invokespecial, SubInvoker si=new SubInvoker(); //invokevirtual si.invokeVirtual(); //invokeinterface si.invokeInterface(); } } class SubInvoker implements IExecutor{ public static void invokeStatic(){ System.out.println("invokestatic was execute"); } public SubInvoker(){ System.out.println("invokespecial was execute in construct"); }; public void invokeVirtual(){ System.out.println("invokevirtual was execute"); } @Override public void invokeInterface() { System.out.println("invokeinterface was execute"); } } interface IExecutor{ public void invokeInterface(); }
反编译后的相关的指令片段:对应main中的方法调用顺序
分派:
众所周知,面向对象的三个特点是:"继承,封装,多态",其中多态又包括覆盖和重载,本节提到的分派就是覆盖和重载的底层实现基础.那么让我们来看看什么是分派?分派和解析属于同一个范畴的概念,都是方法调用的类型而已,只是分派比解析要稍微的复杂一点,分派的符号应用可以再类加载阶段进行转换,也可以再运行时进行转换,而且根据宗量也可能存在单个宗量以及多个宗量,下面将分别对其种类进行说明:
静态分派:
动态分派: