深入理解java虚拟机(10):方法调用

方法调用不等于方法执行,方法调用阶段的唯一任务就是确定被调用的方法的版本。class文件编译期间不包含传统程序的连接过程,因此方法不是实际内存运行的入口地址,这个特性给java带来了动态扩展的能力,也使java的方法调用过程变得更加复杂,需要在类加载期间甚至运行期间才能确定目标方法的引用。

1、解析

方法调用目标方法在class文件里面是对常量池中的一个符号引用,在类加载解析阶段会将其中的一部分符号引用转换成直接引用。转换的前提是在程序真正运行前就有一个可以确定的可用版本,并且这个方法的调用版本在运行期间不可变。换句话说就是调用目标程序代码写好、编译器编译期间就必须确定下来。这类方法调用被称为解析。

符合编译期间可知,运行期间不可变的方法只有静态方法和私有方法。前者与类型直接关联,后者外部不可被访问因此无法被覆盖重写,因此适合在类加载阶段进行解析,与之相对应的是java虚拟机中的5条直接码指令

invokestatic静态方法调用,invokespecial 调用实例构造器init方法,私有方法和类方法,invokevirtual调用虚方法,invokeinteface调用接口方法,运行时确定一个具体实现类的方法,invokedynamic先在运行动态解析出调用点限定符号所引用的方法,再执行该方法。前四条指令的分派逻辑在java虚拟机内部固化,invokedynamic指令的分派逻辑由用户设定的引导方法决定。

解析调用是一个静态的过程,在编译期间就完全确定,在类装载的解析阶段就将符号引用转换成了直接引用,不会延迟到运行期间再去完成。

2、分派

1)静态分派

package org.xiaofeiyang.classloader;

/**
* @author: yangchun
* @description:
* @date: Created in 2019-11-24 11:07
*/
public class StaticDispatch {
static abstract class Human{

}
static class Man extends Human{

}
static class Woman extends Human{

}
public void sayHello(Human guy){
System.out.println("Hello guy");
}
public void sayHello(Man guy){
System.out.println("Hello gentleman");
}
public void sayHello(Woman guy){
System.out.println("Hello lady");
}
public static void main(String[] args){
Human man = new Man();
Human woman = new Woman();
StaticDispatch staticDispatch = new StaticDispatch();
staticDispatch.sayHello(man);
staticDispatch.sayHello(woman);
}
}
运行结果

Hello guy
Hello guy

上面Human类型称为变量的静态类型或者外观类型,静态类型编译期间可知,实际类型动态运行时才可知。上面代码编译器通过参数的静态类型而不是实际类型确定重载方法,所以选择

sayHello(Human guy)作为调用目标,并把这个方法的符号引用写到main()方的两条invokedynamic指令里面。依赖静态类型来定位方法执行版本的分派动作称为静态分派。静态分派发生在编译期间。
package org.xiaofeiyang.classloader;

import java.io.Serializable;

/**
* @author: yangchun
* @description:
* @date: Created in 2019-11-24 11:26
*/
public class Overload {
public static void sayHello(Object arg){
System.out.println("Hello Objec");
}
public static void sayHello(int arg){
System.out.println("Hello int");
}
public static void sayHello(long arg){
System.out.println("Hello long");
}
public static void sayHello(Character arg){
System.out.println("Hello Character");
}
public static void sayHello(char arg){
System.out.println("Hello char");
}
public static void sayHello(char ...arg){
System.out.println("Hello char..");
}
public static void sayHello(Serializable arg){
System.out.println("Hello Serializable");
}
public static void main(String[] args){
sayHello("a");
}
}
这个代码可以非常好的验证静态分派。
2)动态分派



猜你喜欢

转载自www.cnblogs.com/xiaofeiyang/p/11921710.html