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

Java 动态绑定是如何在JVM中发生的?,java,dynamic,binding,Java,Dynamic,Binding,Java中的动态绑定发生在重写函数的运行时。我想知道它是如何在内部发生的(像C++中,使用虚拟函数/表)。p> Java中所有非最终非私有非静态方法都是虚拟的JVM规范描述了JVM必须如何解析虚拟方法: 这一部分最为贴切: 否则,如果C有一个超类,则使用C的直接超类递归执行相同的查找过程;要调用的方法是递归调用此查找过程的结果 递归的具体发生方式取决于JVM编写器如何实现它。这里有一个链接描述了它在OpenJDK中的实现方式(包括到C++代码的链接): 如上所述,在Java中,所有非最终非私有、

Java中的动态绑定发生在重写函数的运行时。我想知道它是如何在内部发生的(像C++中,使用虚拟函数/表)。p> Java中所有非最终非私有非静态方法都是虚拟的

JVM规范描述了JVM必须如何解析虚拟方法:

这一部分最为贴切:

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

递归的具体发生方式取决于JVM编写器如何实现它。这里有一个链接描述了它在OpenJDK中的实现方式(包括到C++代码的链接):

如上所述,在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
    }
}
我们可以看到
任何哺乳动物的字节码。speak()
人类哺乳动物的
speak()
是相同的(
invokevirtual#4//Method org/programming/mitra/exercises/OverridingInternalExample$emdama.speak:()V
),因为根据编译器,这两种方法都是在哺乳动物引用时调用的

所以现在的问题是,如果两个方法调用具有相同的字节码,那么JVM如何知道调用哪个方法

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

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

JVM使用<代码>调用No/Value>指令调用java等价的C++ C++虚拟方法。在C++中,如果我们想重写另一个类中的一个方法,则需要声明为“代码>虚拟< /COD>”,但是在爪哇中,所有方法默认是虚拟的(除了最终方法和静态方法),因为我们可以重写子类中的每个方法。p> 操作

invokevirtual
接受指向方法引用调用的指针(
#4
常量池的索引)

方法引用#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虚拟机实现中,对类实例的引用是指向句柄的指针,句柄本身就是一对指针:一个指向包含对象方法的表,另一个指向表示对象类型的类对象的指针,另一个是从堆中为对象数据分配的内存

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

  • 指向表的指针,该表再次保存对象的方法和指向类对象的指针。e、 g.[speak(),speak(String)类对象]
  • 指向堆上为该对象数据分配的内存的指针,例如实例变量的值
  • 但问题又来了,
    invokevirtual
    如何在内部做到这一点?嗯,没有人能回答这个问题,因为它取决于JVM的实现,而且JVM之间的差异很大

    从上面的语句中,我们可以得出结论,一个对象引用间接地持有一个指向一个表的引用/指针,该表持有该对象的所有方法引用。java从C++中借用了这个概念,这个表是由各种名称所知道的,例如. 我们无法确定
    vtable
    是如何在Java中实现的,因为它依赖于JVM。但是我们可以预料到,它将遵循与C++相同的策略,其中代码> VTAB< /COD>是一种类似数组的结构,它保存方法名称及其数组索引的引用。每当JVM试图执行一个虚拟方法时,它总是向
    vtable
    请求它的地址

    每个类只有一个
    vtable
    ,这意味着它对于类似于
    class
    对象的类的所有对象都是唯一且相同的。在我的文章和文章中,我已经详细讨论了
    对象

    因此,对于
    Object
    类,只有一个
    vtable
    ,它包含所有11个方法(如果我们不计算注册表项的话)和对各自方法体的引用

    当JVM将
    哺乳动物
    类加载到内存中时,它会为它创建一个
    对象,并创建一个
    vtable
    ,其中包含来自对象类vtable的所有方法以及相同的引用(因为
    哺乳动物
    不会覆盖来自对象的任何方法)并为
    speak
    方法添加一个新条目

    现在轮到
    人类
    类了,现在JVM将把
    哺乳动物
    类的
    vtable
    中的所有条目复制到
    人类
    vtable
    中,并为重载版本的
    speak(String)
    添加一个新条目

    JVM知道
    Human
    类重写了两个方法,一个是
    toString()
    来自
    Object
    ,第二个是
    speck()
    来自
    哺乳动物。现在,不再使用更新的引用为这些方法创建新条目。JVM将修改对已经存在的方法的引用,这些方法位于之前存在的相同索引上,并且将保留相同的方法名称

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

    #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