+-所有的目标方法在Class文件中都只是一个符号引用, 在真正执行的阶段,才会将符号引用转为直接引用

将方法从一个符号引用转为一个真正可以执行的调用版本,这个版本在整个运行期间是不可改变的,这就是解析的作用

Java会符合编译器可知,运行期不可变的要求,分为了静态方法和私有方法,前者和类型直接关联,后者在外部不可被访问

在Java中提供集中调用字节码的指令

调用静态方法 invokestatic 调用静态方法

调用实例构造器 init 私有方法和父类方法 invokespecial

调用所有的虚方法 invokevirtual

调用接口方法,运行时候出现一个实现这个接口的对象 invokeinterface

运行时候动态解析出所引用的方法,执行该方法的指令 invokedynamic

只要能在解析阶段确定方法版本的方法就称为非虚方法

静态方法 私有方法 实例构造器 父类方法 final方法

与之相对就是虚方法

在编译阶段将设计的符号引用变成了可以确定的直接引用,不会延迟到运行期去完成,然后将执行的具体方法代码块进行分配,

不过分派可以分为 静态的和动态的

单分派和多分派

可以组合成为静态单分派,静态多分派 动态单分派 动态多分派

然后说一下分派

这一阶段,是指虚拟机如何正确的确定目标方法

也就是父类子类,重载重写的方法

之前说了,分派可以分为 静态 动态两种分配方式

1.静态分配

首先看一下重载的静态分配

图片

图片

图片

最后执行的静态方法是Hunm

上面的Human成为变量的静态类型,或者叫外观类型,后面的Man是实际类型

静态类型和实际类型在程序中都可以发生一些变化,区别是静态类型是在编译期间就已经确定好的

而实际类型只有运行期间才能确定

上面的代码,定义了两个静态类型一致但是实际类型不同的变量,但重载是依靠的静态类型而不是实际类型作为判定依据的,静态类型编译期间可以知道,所以选择了Human本身作为调用方法

再往深走

图片

上面的代码帮助我们再做一次测试,依次的尝试如何去寻找更加合适的静态类型

对于上面的多个重载,必须确定一个更加合适的版本,我们可以依次的尝试如何去寻找更加合适的静态类型

传入一个char类型,会查找char类型的重载方法

如果将char类型的方法注释掉,会调用int类型的方法

如果再将int类型的方法注释掉,会调用long类型的方法

如果将long类型的方法注释掉,回调用自动装箱的方法,而且顺便一说,如果存在byte short类型的重载,也不会往上面匹配,因为char到byte或者short的转型是不安全

如果Character的方法也被注释掉了,会调用Seializable,因为序列化是Character类实现的一个接口,当自动装箱后还是找不到装箱类,但是有其实现的接口类型,也行

在最后连接口都没有的话,会调用Object类型的方法,这就是向上转型了

在最后,才会轮到可变长度的方法,其优先度是最低的

为了满足重写的需求,还出现了动态分派,

图片

那么其执行结果为

很正常了前两个必然是man和woman,最后一个则为woman

那么Java是如何知道要调用哪个方法的

再也不是通过静态类型来确定的,因为对Human两个对象调用了两个不同的方法

这是利用了实际类型,那么Java如何知道实际类型的呢

这是在执行期间,利用了invokevirtual指令的多态查找来执行的,其解析的过程分为

1.找到一个操作数栈顶指向对象的实际类型

2,然后根据时机类型找到符合描述符和简单名称都相符的方法,进行校验,通过直接返回这个方法

3.不然根据继承关系,从下到上对父类进行搜索

4.搜索到最后都没有的话,会抛出一个异常

这就是动态分配,也是方法重写的本质

1.在不考虑对基本类型自动装拆箱(auto-boxing,auto-unboxing) 以及可变长参数的情况下选取重载方法

2.如果找不到,那么就在允许自动装拆箱,但不可变长的情况下选取重载方法

3.如果在第二步找不到合适的方法,就允许自动装拆箱和可变长参数的情况下选取重载方法

单分派和多分派

方法的接受者和方法的参数称为方法的宗量,根据分派基于多少种宗量,分为单分派和多分派,单分派是一个宗量对目标方法进行选择,多分派则是多个宗量进行选择

图片

最后执行时

father的360和son的qq

也就是,在动态分派的时候进行确定了具体执行的对象,在静态分配的时候,确定了是QQ还是360,

现在的Java仍然是一个静态多分派,动态单分派的语言

虚拟机如何实现的动态分派,之前说了虚拟机会对动态分派的问题怎么处理,具体怎么做

由于动态的分派很频繁,所以需要虚拟机在实际过程中,做到高效和稳定

最常用的稳定优化的手段是类在方法区中建立一个虚方法表,所用虚方法表的索引来代替元数据来提高性能

图片

虚方法表中存放了各个方法的实际入口地址,如果子类中没有重写这个方法,那么虚方法表中地址入口和父中的地址入口是一致的,都指向了父类的实现入口,如果子类中重写了,才会替换为子类实现版本的入口地址

而且为了查找上的方便,可以让父类和子类的虚方法表中就有相同的所以序号,这样类型变化的时候,仅仅变更查找的表就可以了

发表评论

邮箱地址不会被公开。 必填项已用*标注