zoukankan      html  css  js  c++  java
  • 关于java的静态绑定与动态绑定小结

    一、问题

    Java方法调用过程中,Jvm是如何知道调用的是哪个类的方法?Jvm又是如何处理?
     
    二、概念
    a、当子类和父类(接口和实现类)存在同一个方法时,子类重写父类(接口)方法时,程序在运行时调用的方法时,是调用父类(接口)的方法呢?还是调用子类的方法呢?我们将确定这种调用何种方法的操作称之为绑定。
     绑定又分为静态绑定和动态绑定。
     
    静态绑定
    静态绑定是在程序执行前就已经被绑定了(也就是在程序编译过程中就已经知道这个方法是哪个类中的方法)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class StaticBindDemo {
      
    public static void s1() {
    System.out.println("static s1");
    }
      
    private void p1() {
    System.out.println("private p1");
    }
      
    public final void f1() {
    System.out.println("final f1");
    }
    }

      

     
    调用方:
    1
    2
    3
    4
    5
    6
    7
    public class StaticCall {
    public static void main(String[] args) {
    StaticBindDemo sbd = new StaticBindDemo();
    StaticBindDemo.s1();
    sbd.f1();
    }
    }

      

     
    反编译后的文件:
     
     
    上面的源代码反编译后,我们可以看到
    调用的是静态方法
    1
    8: invokestatic #4 // Method com/jstar/jvm/sync/bind/StaticBindDemo.s1:()V
     
    1、#4指的是常量沲中的第4个常量表索引项,记录的是方法s1的符号引用,jvm会根据这个符号引用找到方法f1
    所在的类的全限定名: com/jstar/jvm/sync/bind/StaticBindDemo
    2、紧接着JVM会加载、边接和初始化类StaticBindDemo类
    3、然后在StaticBindDem类所在的方法区中找到s1()方法的直接地址,并将这个直接地址记录到StaticCall类的常量池索引为4的常量表中。这个过程叫常量池解析 ,以后再次调用StaticBindDemo.s1时,将直接找到s1方法的字节码;
    4、 完成了StaticCall类常量池索引项4的常量表的解析之后,JVM就可以调用s1()方法,并开始解释执行f1()方法中的指令了。
     
    通过上面的过程,我们发现经过常量池解析之后,JVM就能够确定要调用的s1()方法具体在内存的什么位置上了。实际上,这个信息在编译阶段就已经在StaticCall类的常量池中记录了下来。这种在编译阶段就能够确定调用哪个方法的方式,我们叫做静态绑定机制
     
    注:Java中只有private、static和final修饰的方法以及构造方法是静态绑定。
    a、private方法的特点是不能被继承,也就是不存在调用其子类的对象,只能调用对象自身,因此private方法和定义该方法的类绑定在一起。
     
    b、static方法又称类方法,类方法属于类文件。它不依赖对象而存在,在调用的时候就已经知道是哪个类的,所以是类方法是属于静态绑定。
     
    c、final方法:final方法可以被继承,但是不能被重写,所以也就是说final方法是属于静态绑定的,因为调用的方法是一样的。
     总结:如果一个方法不可被继承或者继承后不可被覆盖,那么这个方法就采用的静态绑定。
     
     
    动态绑定
    编译器在每次调用方法时都要进行搜索,时间开销相当大。因此虚拟机会预先为每个类创建一个方发表(method table),其中列出了所有方法的签名和实际调用的方法。  
     
    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
    public class Father {
    public void f1() {
    System.out.println("father-f1");
    }
      
    public void f1(int i) {
    System.out.println("father-f1 params-int :" + i);
    }
    }
      
    public class Son extends Father{
    public void f1() {
    System.out.println("son f1");
    }
      
    public void f1(char c) {
    System.out.println("son-f1 params-c:" + c);
    }
    }
    public class Demo {
    public static void main(String[] args) {
    Father f = new Son();
    f.f1();
    //f.f1('c');
    }
    }

      

     
    通过反编译Demo我们来看看jvm是怎么执行的
     

     

    其中invokevirtual指令的详细调用过程是这样的:
    (1) invokevirtual指令中的#4指的是Demo类的常量池中第4个常量表的索引项。这个常量表(Methodref ) 记录的是方法f1信息的符号引用(包括f1所在的类名,方法名和返回类型)。JVM会首先根据这个符号引用找到调用方法f1的类的全限定名: Father。这是因为调用方法f1的类的对象father声明为Father类型。
    (2) 在Father类型的方法表中查找方法f1,如果找到,则将方法f1在方法表中的索引项记录到Demo类的常量池中第4个常量表中(常量池解析 )。这里有一点要注意:如果Father类型方法表中没有方法f1,那么即使Son类型中方法表有,编译的时候也通过不了。因为调用方法f1的类的对象father的声明为Father类型。
    (3) 在调用invokevirtual指令前有一个aload_1指令,它会将开始创建在堆中的Son对象的引用压入操作数栈。然后invokevirtual指令会根据这个Son对象的引用首先找到堆中的Son对象,然后进一步找到Son对象所属类型的方法表.
    (4) 这是通过第(2)步中解析完成的#4常量表中的方法表的索引项,可以定位到Son类型方法表中的方法f1(),然后通过直接地址找到该方法字节码所在的内存空间。
    很明显,根据对象(father)的声明类型(Father)还不能够确定调用方法f1的位置,必须根据father在堆中实际创建的对象类型Son来确定f1方法所在的位置。这种在程序运行过程中,通过动态创建的对象的方法表来定位方法的方式,我们叫做 动态绑定机制 。
     
    动态绑定过程: 
     <1>虚拟机提取对象的实际类型的方法表。 
     <2>虚拟机搜索方法签名,此时虚拟机已经知道应该调用哪种方法。(PS:方法的签名包括了:1.方法名 2.参数的数量和类型~~~~返回类型不是签名的一部分。) 
     <3>虚拟机调用方法
     
     
    转载自https://www.cnblogs.com/xyz-star/p/10152676.html
     
     
  • 相关阅读:
    使用Identity Server 4建立Authorization Server (3) yangxu
    Asp.Net Core 之 基于 Open Connect ID 身份验证
    Pandas数据结构 2
    Pandas 数据结构 DataFrame
    大数据加工平台数据清洗
    Python电影数据分析
    Pandas安装
    Pandas 读取CSV
    Mongo Python 增、删、改、查等操作
    读书笔记人月神话其三
  • 原文地址:https://www.cnblogs.com/xiaxiaopi/p/14329961.html
Copyright © 2011-2022 走看看