• java分派


    分派的说明

    分派的目的是为了解决虚方法的引用问题的,因为虚方法的引用不是不变的,虚方法可能由于继承被重写或者由于参数变化重载了,在调用的过程中确定是调用该类还是父类的方法是至关重要的,因此产生了分派技术进行问题解决。
    此外要知道 B b = new a()中,对象的指定类型B是静态类型,这个是确定好的,编译可知的,而a为实际类型是初始化的时候通过动态链接找到的

    静态分派

    虚拟机在处理重载时是通过参数的静态类型而不是实际类型作为判断依据的,并且上面提到了,静态类型是编译期可知的。因此,在编译阶段,编译器会根据参数的静态类型决定使用哪个重载版本,选择了方法的重载版本之后,编译器会把这个方法的符号引用写到方法调用字节码指令的参数中。

    class Animal1 {}
    class Dog1 extends Animal1{}
    class Cat1 extends Animal1{}
    class Execute1{
        public void execute(Animal1 a){
            System.out.println("animal");
        }
        public void execute(Dog1 a){
            System.out.println("dog");
        }
        public void execute(Cat1 a){
            System.out.println("cat");
        }
    }
    class Client1{
        public static void main(String[] args) {
            Animal1 a=new Animal1();
            Animal1 b=new Dog1();
            Animal1 c=new Cat1();
            Execute1 execute=new Execute1();
            execute.execute(a);
            execute.execute(b);
            execute.execute(c);
            //输出结果
            //animal
            //animal
            //animal
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
     0 new #2 <src/fenPai/Animal1>
     3 dup
     4 invokespecial #3 <src/fenPai/Animal1.<init> : ()V>
     7 astore_1
     8 new #4 <src/fenPai/Dog1>
    11 dup
    12 invokespecial #5 <src/fenPai/Dog1.<init> : ()V>
    15 astore_2
    16 new #6 <src/fenPai/Cat1>
    19 dup
    20 invokespecial #7 <src/fenPai/Cat1.<init> : ()V>
    23 astore_3
    24 new #8 <src/fenPai/Execute1>
    27 dup
    28 invokespecial #9 <src/fenPai/Execute1.<init> : ()V>
    31 astore 4
    33 aload 4
    35 aload_1
    36 invokevirtual #10 <src/fenPai/Execute1.execute : (Lsrc/fenPai/Animal1;)V>
    39 aload 4
    41 aload_2
    42 invokevirtual #10 <src/fenPai/Execute1.execute : (Lsrc/fenPai/Animal1;)V>
    45 aload 4
    47 aload_3
    48 invokevirtual #10 <src/fenPai/Execute1.execute : (Lsrc/fenPai/Animal1;)V>
    51 return
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    动态分派

    方法重写被被调用的过程中调用字节码指令(这里是invokevirtual)和指令的参数(Animal .execute()的符号引用)都是一样的,但是最终的执行目标并不相同(一个Dog ,一个Cat)。这里就涉及到invokevirtual指令的多态查找过程:

    1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记做M
    2. 如果在类型M中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,若通过则返回这个方法的直接引用,查找过程结束;否则则返回IllegalAccessError异常
    3. 否则,按照继承关系从下往上依次对M的各个父类进行第2步的搜索和验证过程
      如果始终没有找到合适的方法,则抛出AbstractMethodError异常

    dog.execute();语句的执行过程是先把dog实例对象压到栈顶,然后通过invokevirtual指令调用,这个dog对象称为execute()方法的接收者(Receiver)。从上面步骤可以看到,第一步就是在运行期确定执行execute方法的接收者实际类型为dog。cat.execute();语句同理。所以两次调用中的Animal .execute()符号引用被解析到了不同的直接引用上,这个过程就是Java方法重写的本质。这种在运行期根据实际类型确定方法执行版本的分派过程就称为动态分派。

    在这里插入代码片
    
    • 1
    public class Animal {
        public void execute(){
            System.out.println("animal");
        }
    }
    class Dog extends Animal{
        @Override
        public void execute() {
            System.out.println("dog");
        }
    }
    class Cat extends Animal{
        @Override
        public void execute() {
            System.out.println("cat");
        }
    }
    class Client{
        public static void main(String[] args) {
            Animal a1=new Animal();
            Animal a=new Dog();
            Animal b=new Cat();
            a1.execute();
            a.execute();
            b.execute();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
     0 new #2 <src/fenPai/Animal>
     3 dup
     4 invokespecial #3 <src/fenPai/Animal.<init> : ()V>
     7 astore_1
     8 new #4 <src/fenPai/Dog>
    11 dup
    12 invokespecial #5 <src/fenPai/Dog.<init> : ()V>
    15 astore_2
    16 new #6 <src/fenPai/Cat>
    19 dup
    20 invokespecial #7 <src/fenPai/Cat.<init> : ()V>
    23 astore_3
    24 aload_1
    25 invokevirtual #8 <src/fenPai/Animal.execute : ()V>
    28 aload_2
    29 invokevirtual #8 <src/fenPai/Animal.execute : ()V>
    32 aload_3
    33 invokevirtual #8 <src/fenPai/Animal.execute : ()V>
    36 return
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    双分派

    双分派就是利用方法的重写和重载的组合来完成指定操作的过程

    class Animal2 {
        public void accetp(Execute2 execute){
            execute.execute(this);
        }
    }
    class Dog2 extends Animal2 {
        public void accetp(Execute2 execute){
            execute.execute(this);
        }
    }
    class Cat2 extends Animal2 {
        public void accetp(Execute2 execute){
            execute.execute(this);
        }
    }
    class Execute2{
        public void execute(Animal2 a){
            System.out.println("animal");
        }
        public void execute(Dog2 a){
            System.out.println("dog");
        }
        public void execute(Cat2 a){
            System.out.println("cat");
        }
    }
    class Clients{
        public static void main(String[] args) {
            Animal2 a=new Animal2();
            Animal2 b=new Dog2();
            Animal2 c=new Cat2();
            Execute2 execute=new Execute2();
            a.accetp(execute);
            b.accetp(execute);
            c.accetp(execute);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    JVM重载的实现

    目前Java语言是一门静态多分派、动态单分派的语言。
    方法的接收者方法的参数统称为方法的宗量
    静态分派也就是重载,要看方法的接收者方法的参数(需要看谁调用了这个方法,这个方法的参数类型是什么,不同的类型使用不同的参数,因为是重载函数嘛)因此是多分派
    动态分派也就是重写,只需看方法的接收者(只看谁调用的就行,参数是唯一确定)因此是单分派

    • 动态分派是非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在类的方法元数据中搜索合适的目标方法,因此处于性能考虑,虚拟机做出了优化:为类在方法区中建立一个虚方法表(Virtual Method Table,与此对应的,在invokeinterface执行时也会用到接口方法表—Interface Method Table),虚方法表中存放着各个方法的实际入口地址。
    • 如果某个方法在子类中没有被重写,那么子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的,都指向父类的实现入口;如果子类重写了这个方法,那么子类方法表中虚方法表中的地址将会替换为指向子类实现版本的入口地址。这样通过冗余存放的方式,在运行时搜索目标方法的时候,就不用依次对对象的各个父类进行搜索了。
    • 同时,具有相同签名的方法,在父类、子类的虚方法表中都应具有一样的索引号,这样当类型转换时,只需要变更查找的方法表,就可以从不同的虚方法表中按索引转换出所需的入口地址。方法表一般在类加载的连接阶段(准备阶段)进行初始化,准备了类的变量初始值后,虚拟机会把该类的方法表也初始化完毕。
      除了使用方法表外,在条件允许的情况下,虚拟机还会使用内联缓存(Inline Cache)和基于”类型继承关系分析(Class Hierarchy Analysis,CHA)“技术的守护内联(Guarded Inlining)两种手段来获得更高的性能。
  • 相关阅读:
    记录自签tomcat所用TLS1.2链接所需SSL证书
    阿里云解决方案架构师张平:云原生数字化安全生产的体系建设
    Linux中DNS的正向和反向解析
    Spring 中容器启动分析之refresh方法执行之前
    Python机器视觉--OpenCV入门--OpenCV鼠标绘制图形
    JAVA基础——day07
    Qt实现右下角消息弹窗
    在阿里内部是如何 Debug 线上问题的?
    alsa-lib和alsa-utils移植
    Linux mmap原理
  • 原文地址:https://blog.csdn.net/qq_37771209/article/details/126342286