在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解析到方法调用的字节码时,会经历如下过程:
- 找到 *** 作数栈顶的第一个元素所执行对象的实际类型C
- 根据类型C去常量池中找描述和名称都符合的方法,如果找到则进行访问权限校验,如果通过则返回该方法的直接引用,如果不通过则抛出java.lang.IllegalAccessError异常
- 如果在步骤2没找到,则说明该方法不在C本类中,那就有可能在C的父类中,于是就需要对C的父类一次执行2的 *** 作
- 如果上述都没有找到,证明调用的方法是接口(抽象方法),没有进行实现,则会抛出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会把该类的方法表也初始化完毕。
虚方法表的示例图如下:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)