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

Blu*_*ond 5 java binding dynamic

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

Nar*_*shi 8

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

\n\n

但是为了理解 JVM 如何在内部处理它,让我们看下面的代码示例

\n\n
public class OverridingInternalExample {\n\n    private static class Mammal {\n        public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }\n    }\n\n    private static class Human extends Mammal {\n\n        @Override\n        public void speak() { System.out.println("Hello"); }\n\n        // Valid overload of speak\n        public void speak(String language) {\n            if (language.equals("Hindi")) System.out.println("Namaste");\n            else System.out.println("Hello");\n        }\n\n        @Override\n        public String toString() { return "Human Class"; }\n\n    }\n\n    //  Code below contains the output and and bytecode of the method calls\n    public static void main(String[] args) {\n        Mammal anyMammal = new Mammal();\n        anyMammal.speak();  // Output - ohlllalalalalalaoaoaoa\n        // 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V\n\n        Mammal humanMammal = new Human();\n        humanMammal.speak(); // Output - Hello\n        // 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V\n\n        Human human = new Human();\n        human.speak(); // Output - Hello\n        // 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V\n\n        human.speak("Hindi"); // Output - Namaste\n        // 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n

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

\n\n
\n

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

\n
\n\n

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

\n\n

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

\n\n
invokevirtual #4   // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V\n
Run Code Online (Sandbox Code Playgroud)\n\n

该方法引用\xc2\xa0#4\xc2\xa0再次指的是方法名称和类引用

\n\n
#4 = Methodref   #2.#27   // org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V\n#2 = Class   #25   // org/programming/mitra/exercises/OverridingInternalExample$Mammal\n#25 = Utf8   org/programming/mitra/exercises/OverridingInternalExample$Mammal\n#27 = NameAndType   #35:#17   // speak:()V\n#35 = Utf8   speak\n#17 = Utf8\n
Run Code Online (Sandbox Code Playgroud)\n\n

所有这些引用结合起来用于获取对方法和要在其中找到该方法的类的引用。JVM规范中也提到了这一点

\n\n
\n

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

\n
\n\n

书签 4 状态

\n\n
\n

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

\n
\n\n

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

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

但问题又来了,invokevirtual内部如何做到这一点?好吧,没有人能回答这个问题,因为它取决于 JVM 的实现,并且根据 JVM 的不同而不同。

\n\n

从上面的陈述中,我们可以得出结论,对象引用间接保存了一个指向表的引用/指针,该表保存了该对象的所有方法引用。Java从C++借用了这个概念,这个表有各种名称,例如虚拟方法表(VMT)、虚拟函数表(vftable)、虚拟表(vtable)、调度表

\n\n

我们无法确定vtableJava 中是如何实现的,因为它依赖于 JVM。但我们可以预期它将遵循与 C++ 相同的策略,其中vtable是一个类似数组的结构,它保存方法名称及其对数组索引的引用。每当 JVM 尝试执行虚拟方法时,它总是会询问vtable它的地址。

\n\n

每个类只有一个,vtable这意味着对于类似于\xc2\xa0Class对象的类的所有对象来说,它是唯一且相同的。Class我在文章《Why an external Java class can\xe2\x80\x99t be static》《Why Java is Purely Object-Oriented Language Or Why Not》中讨论了更多关于对象的内容。

\n\n

因此,只有一个vtableObject包含所有 11 个方法(如果我们不计算 registerNatives)以及对它们各自方法体的引用。

\n\n

对象的虚函数表

\n\n

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

\n\n

人类虚拟表

\n\n

现在轮到Humanclass 了,现在 JVM 会将所有条目从vtableof Mammalclass 复制到vtableof Human,并为 的重载版本添加一个新条目speak(String)

\n\n

JVM 知道该类Human重写了两个方法,第一个是toString()from Object,第二个是speck()from Mammal。现在,不要使用更新的引用为这些方法创建新条目。JVM 将修改对先前存在的同一索引上已存在方法的引用\xc2\xa0,并将保留相同的方法名称。

\n\n

\n\n

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

\n\n

我希望现在大家已经清楚 JVM 如何混合constant pool条目并vtable推断出它将调用哪个方法。

\n\n

您可以阅读我的文章《JVM 如何在内部处理方法重载和重写》来了解更多信息。

\n


dka*_*zel 5

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