JVM-方法调用
内存角度
方法的执行原理:每一次方法调用都会生成一个栈帧并压入栈中,方法链的执行就是一个个栈帧弹出栈的过程。
方法调用指令
Java中有非虚方法和虚方法,前者是指在解析阶段可以确定的唯一的调用版本,如静态方法、构造器方法、父类方法(特指在子类中使用super调用,而不是在客户端使用对象引用调用)、私有方法(上述几种方法是使用invokestatic和invokespecial指令调用的)以及被final修饰的方法(使用invokevirtual调用),这些方法在类加载阶段就会把方法的符号引用解析为直接引用;除此之外的都是虚方法,虚方法则只能在运行期进行分派调用。
Java中方法的调用对应字节码有5条指令:
- **
解析**调用:在编译期就可知的,有一个确定的版本且运行期不可变- invokestatic:用于调用**
静态**方法。 - invokespecial:用于调用实例**
构造器方法、私有方法和父类方法(super**调用)。
- invokestatic:用于调用**
- **
分派**调用- invokevirtual:用于调用所有的**
虚方法**。 - invokeinterface:用于调用**
接口方法**,会在运行时再确定一个实现该接口的对象。 - invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。
- invokevirtual:用于调用所有的**
invokedynamic与前4条指令不同的是,该指令分派的逻辑是由用户指定,用于支持动态类型语言特性。
分派调用的实现
- 方法表:虚拟机在方法区会为**
每个类型建立一个虚方法表**(支持invokevirtual 指令)以及接口方法表(支持invokeinterface指令),方法表中存的是各个方法的实际入口地址,如果子类没有重写父类中的方法,那么父子类指向同一个地址,否则,子类就会指向自己重写后的方法入口地址 - 实现过程:访问栈上的调用者,读取调用者的动态类型,读取该类型的方法表,读取方法表中某个索引值所对应的目标方法。
- 在方法表的基础上,动态绑定的优化技术有**
内联缓存、方法内联**
分派调用总结
分派又分为静态分派、动态分派:
**
重载属于静态分派,在编译期间能根据参数的静态类型**决定使用哪一版本的方法,编译期间调用的对象是可以确定的**
重写属于动态分派,在运行期间进行符号引用解析,根据接收者**的实际类型决定调用方法的版本。解析和分派时多态的一个实现,这种多态是方法的多态。但是字段不支持多态,父类字段名和子类字段名相同时,子类对象会存在两个字段,但是他们会被分开在所属类的空间中,父类方法访问的时父类的字段,子类方法访问的是子类的字段。
静态分派、动态分派是JVM的方法调用的两个步骤:
- 概念:JVM识别方法的关键在于**
类名、方法名以及方法描述符**,方法描述符是由方法的参数类型以及返回类型所构成 - (
类名)先确定调用者是谁,获取到调用者的**实际类型,然后找到调用者实际类型的方法表,这是动态分派**(方法重写)的过程 - (
方法名、方法描述符)根据方法名以及方法描述符,在方法表中查找方法引用,这是**静态分派**(方法重载)的过程
- 概念:JVM识别方法的关键在于**