方法的静态动态分派的学习笔记

方法的静态动态分派

有些符号引用是在类加载阶段或是第一次使用时就会转换为直接引用,这种转换叫静态解析;另外一些符号引用是每次运行期转换为直接引用,这种转换叫做动态引用,这体现了java的多态性

invokeinterface:调用接口中的方法,实际上是在运行期决定的,决定到底调用实现该接口的哪个对象的特定方法。

invokestatic:调用静态方法

invokespecial:调用自己的私有方法,构造方法(<init标识>)以及父类方法。

invokevirtual:调用虚方法,运行期动态查找的过程

invokedynamic:动态调用方法。

静态解析的4中情形:静态方法,父类方法,构造方法,私有方法。以上4类方法称作非虚方法。

公有方法因为可以被子类重写,所以是动态引用。

方法的重载是一种“静态”的行为,编译期就可以完全确定。只需要关注传入的参数的类型。

方法的重写是动态的,是运行期行为

方法的动态分派

Grandpa p1 = new Father();
g1的静态类型是Grandpa,而g1的实际类型(真正指向的类型)是Father

变量的静态类型是不会发生改变的,而变量的实际类型则是可以发生变化的(多态的一种体现)实际类型在运行期方可确定

从invokevirtual指令的多态查找过程开始说起,invokevirtual指令的运行时解析过程大致分为以下几个步骤:

1、找到操作数栈顶的第一个元素所指向的对象实际类型,记做C。

2、如果在类型C中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。

3、否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证操作。

4、如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常

由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,这个过程就是java语言中方法重写的本质。我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派。

针对方法调用动态分派的过程,虚拟机会在类的方法区建立一个虚方法表的数据结构,(vtable)

虚方法表vtable中每一项都存放的是特定方法实际真正的入口调用地址,其中有一种情况,就是子类Dog只继承了Animal但没有重写过Animal父类的方法。在Dog类中虚方法表就直接指向父类中没有重写的方法,而非又将父类的方法拷贝一份放到方发表中。这样节省空间。

如果Dog子类重写了父类的方法,那么当然方法就会存在于Dog的虚方法表中啦。所以说对于Object类来说里面定义了很多的方法,但是实际我们编写的类可能很多都没有重写它里面的方法,那么其虚方法表中都是存在Object当中而非拷贝一份到我们具体子类当中。

另外虚方法表vtable还有一点就是:只要是子类和父类的方法描述是一样的,那么它们在父类和子类的索引是一样的,这样当查找子类的方法时,由于索引跟父类是一模一样的,则直接拿着子类该方法的索引到父类的方法表中的对应的索引就直接可以定位到了,比较高效。一般虚方法表都是在类的连接阶段【类加载有加载、连接、初始化阶段】进行的初始化。

针对invokeinterface指令来说,虚拟机会建立一个叫做接口方法表的数据结构(itable)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值