jvm虚拟机栈-方法的调用

在jvm中的方法分为虚方法和非虚方法

非虚方法

如果方法在编译期间就确定了具体的调用版本,这个版本在运行时是不可变得,这样的方法称为非虚方法,具体有静态方法(不能被重写),私有方法(不能被重写),final方法(不能被重写),实例构造器(不能被重写),父类方法(明确地使用super的方法调用父类方法)都是非虚方法。

虚方法

除了非虚方法外的都是虚方法(除了final修饰的之外)

虚与非虚方法对应的普通字节码指令

非虚方法对应的字节码指令:
invokestatic字节码指令:调用静态方法,解析阶段确定唯一方法版本
invokespecial字节码指令:调用init方法,私有及父类方法。

虚方法对应的字节码指令:
invokevirtual字节码指令:调用所有虚方法(除了final修饰的之外)
invokeinterface字节码指令:调用接口方法

下面以实际的代码来做实例

package com.lydon.test;
public class Son extends Father{
    
    
    public Son(){
    
    
        super();
    }

    public Son(String name){
    
    
        this();
    }

    /**
     * 子类自己的静态方法
     */
    public static void showStatic(){
    
    
        System.out.println("son static");
    }

    private void showPrivate(String str){
    
    
        System.out.println("son private"+str);
    }

    public void info(){
    
    
        System.out.println("son info()");
    }

    public void show(){
    
    
        //静态方法
        showStatic();
        super.showStatic();

        //私有方法
        showPrivate("");
        super.showCommon();

        //final方法,因为final不能被重写,所以这里不加super也是非虚方法
        super.showFinal();
        showCommon();

        info();
    }

    public static void main(String[] args) {
    
    
        Son son = new Son();
        son.show();
    }
}


class Father{
    
    
    public Father(){
    
    
        System.out.println("father的构造器");
    }

    public static void showStatic(){
    
    
        System.out.println("father static");
    }

    public final void showFinal(){
    
    
        System.out.println("father show final");
    }

    public void showCommon(){
    
    
        System.out.println("father 普通方法");
    }
}

查看Son类的show方法的字节码:
在这里插入图片描述

一句话总结,不管是子类还是父类,凡是能被重写的方法就是虚方法,子类如果使用super显示调用父类的方法,则就不是虚方法。

动态字节码指令

除了上述四种普通字节码指令外,还有一种动态字节码指令invokedynamic,它在jdk1.8之前不能通过代码的方式生成,在jdk1.8之后因为lambda的引入后(匿名函数不能确定具体的类型),才可以生成,而lambda表达式的引入,使得java从一个纯粹的静态语言(类型的检查是在编译期就检查)变成带有一点动态语言(例如js,python)的语言。示例如下:

package com.lydon.test;


interface Func{
    
    
    public boolean func(String str);
}
public class Lambda {
    
    
    public void lambda(Func func){
    
    
        return;
    }


    public static void main(String[] args) {
    
    
        Lambda lambda = new Lambda();
        //匿名函数
        Func func=s->{
    
    return true;};
        lambda.lambda(func);


        //匿名函数
        lambda.lambda(s->{
    
    return false;});
    }
}

方法调用的本质

在jvm中,非虚方法能确定地找到对应的方法,但对于虚方法来说,由于不能确定调用的虚方法是调用的谁,所以在jvm解析到方法调用的字节码时,会经历如下过程:

  1. 找到操作数栈顶的第一个元素所执行对象的实际类型C
  2. 根据类型C去常量池中找描述和名称都符合的方法,如果找到则进行访问权限校验,如果通过则返回该方法的直接引用,如果不通过则抛出java.lang.IllegalAccessError异常
  3. 如果在步骤2没找到,则说明该方法不在C本类中,那就有可能在C的父类中,于是就需要对C的父类一次执行2的操作
  4. 如果上述都没有找到,证明调用的方法是接口(抽象方法),没有进行实现,则会抛出java.lang.AbstractMethodError

一般abstractMethodError错误发生在jar包版本不一致的情况,例如有jar包a的1.0版本,jarb包b1.0在基于a1.0写了实现它某个接口的方法method1,并编译通过。而后面a调整了该接口,升级到a2.0,但此时b还是没变,此时引用了a2.0和b1.0程序的c在调用b的method1方法时,就会出现这个错误。

在实际场景来说,对于虚方法如果每次都要经历上述寻找的过程,则会影响到执行效率,因此为了提高性能,jvm采用在类的方法区建立虚方法表(只有虚方法的映射),使用索引表来代替查找,提高效率。

虚方法表

  • 每个类中都有虚方法表,表中存放着虚方法的实际入口,毕竟有些虚方法会是父类的方法。
  • 虚方法表会在类加载时的链接阶段被创建并初始化,类的变量初始化完成后,jvm会把该类的方法表也初始化完毕。

虚方法表的示例图如下:在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lyd135364/article/details/120812921