java动态绑定和多态实现原理

     在java方法调用过程中,jvm是如何知道具体调用哪个类的方法? 我们常常听到的静态和动态方法调用有什么区别呢?他们底层的原理是什么?本文将围绕这几个问题展开。

静态绑定:

       上面的源代码中Father.f1()被编译成 invokestatic #13, #13表示StaticCall类的常量池中第13个常量表的索引项,它记录着方法f1的符号引用,包括f1所在的类名,方法名以及返回类型,jvm首先会根据这个符号引用找到方法f1所在的类的全限定名。然后会加载、链接和初始化Father类,接着会在Father类所在的方法区中找到f1()方法的直接地址,并将这个直接地址记录到StaticCall类常量池索引为13的索引项中。这个过程叫常量池解析,以后每次调用 Father.f1()时,jvm将直接找到f1方法的字节码,并开始解释执行。

      上述中,经过常量池解析后,jvm就能够确定调用的f1()方法具体在内存的什么位置,实际上这个信息在第一次解析后就已经在StaticCall类的常量池中记录了下来,这种在解析一次后就能够确定所调用方法的地址的方式叫做静态绑定机制。

除了被static修饰的静态方法,,被final修饰的禁止子类覆盖的方法(invokevirtual),所有类的初始化方法(invokespecial),所有被private修饰的私有方法(invokespecial) ,jvm采用静态绑定机制来调用这些方法。

动态绑定机制:

jvm是如何知道f.f1()调用的是子类Sun中的方法而不是Father中的方法?

     我们知道在jvm加载类的时候,会在方法区为这个类存放很多信息,其中在链接阶段会分配一块空间,这个数据结构叫方法表,它以数组的形式记录了当前类及其所有超类的可见方法字节码在内存的直接地址。


该方法表有两个特点,1: 子类方法表中继承了父类的方法 2: 相同的方法在所有类的方法表中的索引相同。

编译器会把上面main方法编译成下面字节码指令:


其中invokevirtual的指令的详细调用过程如下:

(1)invokevirtual指令中的#15指的是AutoCall类的常量池中的第15个索引项,它记录者方法f1信息的符号引用,jvm首先会根据这个符号引用找到f1的类全限定名,Father, 而不是son是因为调用方法f1的类的对象father声明为Father类型。

(2)在Father类型的方法表中查找方法f1,如果找到,则将方法f1在方法表中的索引项11记录到 AutoCall类的常量池中第15个常量表中(常量池解析),如果Father类型方法表中没有方法f1,即使son类型中方法表有,也会报错!

(3)在invokevirtual指令前有一个aload_1指令,它会把在堆中Son对象的引用压入操作数栈,invokevirtual指令会根据这个son对象引用找到堆中的son对象,然后进一步找到son对象所属类型的方法表,再根据步骤2完成解析的常量表#15的内容11,可以定位到son类型方法表中的方法f1(),可找到该方法字节码所在的内存空间。

    这种通过实际对象所在类的方法表每次来定位方法在内存的具体位置的方式,叫做动态绑定机制,与静态绑定相比,动态绑定的字节码指令在解析常量池时并不会解析出方法具体在内存的位置,而是一个该方法在方法表上的索引,具体调用时根据传入对象的具体类来找到方法在内存的具体位置。

进阶

Father类型中并没有方法签名为f1(char)的方法,但最后却是调用了Father类型中的f1(int)方法,并没有调用到Son类型中的f1(char)方法。实际上,jvm会根据参数的自动转型来找到合适的方法,找到 Father类型中的f1(int),Son类型中并没有重写f1(int)方法,所以还是调用Father的f1(int)方法。


       这个同上一样,jvm会选择合适的方法去调用。选择标准是:如果一个方法可以接受传递给另一个方法的任何参数,那么该方法就相对不合适,应该选择参数范围更小的方法。

总结

       简单地说,静态绑定就是只需要解析一次就能够确定方法在内存的位置,所以可看成是静态的。而动态绑定常量池解析不会解析出方法在内存的具体地址,只是解析出一个方法在方法表上的索引位置,具体在内存的地址需要根据传入的对象的具体类型来确定,也即是动态确定的。

你可能感兴趣的:(java动态绑定和多态实现原理)