动态绑定如何在JVM中发生?

时间:2013-08-06 14:13:56

标签: java dynamic binding

Java中的动态绑定在运行时发生,用于覆盖函数。我想知道它是如何在内部发生的(比如在C ++中,使用虚函数/表)。

3 个答案:

答案 0 :(得分:4)

Java中的所有非最终非私有非静态方法都是虚拟的

答案 1 :(得分:2)

如上所述,在Java中,所有非最终的非私有,非静态方法都是虚拟的,这意味着可以在子类中重写的任何方法都是虚拟的。

但是为了了解JVM内部处理方式,让我们以下面的代码示例为例

public class OverridingInternalExample {

    private static class Mammal {
        public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
    }

    private static class Human extends Mammal {

        @Override
        public void speak() { System.out.println("Hello"); }

        // Valid overload of speak
        public void speak(String language) {
            if (language.equals("Hindi")) System.out.println("Namaste");
            else System.out.println("Hello");
        }

        @Override
        public String toString() { return "Human Class"; }

    }

    //  Code below contains the output and and bytecode of the method calls
    public static void main(String[] args) {
        Mammal anyMammal = new Mammal();
        anyMammal.speak();  // Output - ohlllalalalalalaoaoaoa
        // 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

        Mammal humanMammal = new Human();
        humanMammal.speak(); // Output - Hello
        // 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

        Human human = new Human();
        human.speak(); // Output - Hello
        // 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V

        human.speak("Hindi"); // Output - Namaste
        // 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
    }
}

我们可以看到anyMammal.speak()humanMammal.speak()的字节码是相同的(invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V),因为根据编译器,这两种方法都是在哺乳动物参考上调用的。

现在问题来了,如果两个方法调用都具有相同的字节码,那么JVM如何知道要调用哪个方法?

好吧,根据JVM规范,答案隐藏在字节码本身中,并且是invokevirtual指令

  

invokevirtual调用对象的实例方法,并根据对象的(虚拟)类型进行分派。这是Java编程语言中的常规方法分派。

JVM使用invokevirtual指令来调用等效于C ++虚拟方法的Java。在C ++中,如果我们想覆盖另一个类中的一个方法,则需要将其声明为virtual,但是在Java中,默认情况下,所有方法都是虚拟的(final和static方法除外),因为我们可以覆盖子级中的每个方法类。

操作invokevirtual接受一个指向方法引用调用的指针(#4进入常量池的索引)

invokevirtual #4   // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

该方法引用#4再次引用方法名称和类引用

#4 = Methodref   #2.#27   // org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
#2 = Class   #25   // org/programming/mitra/exercises/OverridingInternalExample$Mammal
#25 = Utf8   org/programming/mitra/exercises/OverridingInternalExample$Mammal
#27 = NameAndType   #35:#17   // speak:()V
#35 = Utf8   speak
#17 = Utf8

所有这些引用共同用于获取对要在其中找到该方法的方法和类的引用。 JVM规范

中也提到了这一点。
  

Java虚拟机不要求对象4具有任何特定的内部结构。

书签4个状态

  

在Oracle对Java虚拟机的某些实现中,对类实例的引用是指向本身是一对指针的句柄的指针:一个指向包含对象的方法的表和指向Class的指针表示对象类型的对象,另一个表示从堆为对象数据分配的内存。

这意味着每个引用变量都包含两个隐藏的指针

  1. 指向表的指针,该指针再次保存对象的方法和指向Class对象的指针。例如[speak(),speak(String)类对象]
  2. 指向该对象的数据在堆上分配的内存的指针,例如实例变量的值。

但是问题又来了,invokevirtual在内部如何做到这一点?嗯,没人能回答这个问题,因为它取决于JVM的实现,并且因JVM而异。

从上面的陈述中,我们可以得出结论,对象引用间接持有一个表的引用/指针,该表保存了该对象的所有方法引用。 Java从C ++借用了这个概念,并且该表以各种名称(例如virtual method table ( VMT ), virtual function table ( vftable ), virtual table ( vtable ), dispatch table)而闻名。

我们不确定vtable是如何在Java中实现的,因为它依赖于JVM。但是我们可以预料它将采用与C ++相同的策略,其中vtable是类似数组的结构,该结构将方法名及其引用保留在数组索引上。每当JVM尝试执行虚拟方法时,它总是向vtable询问其地址。

每个类只有一个vtable,这意味着它对于与Class对象相似的类中的所有对象都是唯一的。我在文章Why an outer Java class can’t be staticWhy Java is Purely Object-Oriented Language Or Why Not中讨论了Class对象的更多信息。

因此vtable类只有一个Object,其中包含所有11个方法(如果不计算registerNatives的话)以及对它们各自方法主体的引用。

vtable-of-object

当JVM将Mammal类加载到内存中时,它会为其创建一个Class对象,并创建一个vtable,其中包含Object类的vtable中所有具有相同引用的方法(因为Mammal不会覆盖Object中的任何方法,而是为speak方法添加了一个新条目。

vtable-human

现在轮到Human类了,现在JVM会将所有条目从vtable类的Mammal复制到vtable的{​​{1}},然后为Human的重载版本添加新条目。

JVM知道speak(String)类重写了两种方法,一种是Human中的toString(),第二种是Object中的speck()。现在,而不是使用更新的引用为这些方法创建新条目。 JVM将修改对早先存在的相同索引上已经存在的方法的引用,并将保留相同的方法名称。

Mammal使JVM将方法引用invokevirtual处的值视为地址,而是作为要在#4中查找当前对象的方法的名称。

我希望现在弄清楚JVM如何将vtable条目和constant pool混合在一起以得出将要调用的方法的结论。

您可以阅读我的文章How Does JVM Handle Method Overloading and Overriding Internally的更多信息。

答案 2 :(得分:0)

JVM规范描述了JVM必须如何解决虚拟方法:http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokevirtual

这部分是最相关的:

  

否则,如果C具有超类,则使用C的直接超类递归地执行相同的查找过程;要调用的方法是递归调用此查找过程的结果。

究竟如何进行递归取决于JVM编写器如何实现它。这是一个描述如何在OpenJDK中完成的链接(包括指向C ++代码的链接): https://wikis.oracle.com/display/HotSpotInternals/VirtualCalls