2

我有以下java代码:

class Father  {
    public void walk(int x) { System.out.format("Fwalk %d", x); }
    public void run (int x) { System.out.format("Frun  %d", x); }
    public void swim(int x) { System.out.format("Fswim %d", x); }
}
class Son extends Father {
    public void swim(int x) { System.out.format("Fswim %d", x); }
    public void cook(int x) { System.out.format("Scook %d", x); }
    public void run (int x) { System.out.format("Frun  %d", x); }
}
class main {
    public static void main(String[] args) {
        Father f = new Father();
        f.run(1);
        f.swim(2);
        Son s = new Son();
        s.run(3);
        s.swim(4);
    }
}

我正在寻找虚函数表的实际偏移量。当我使用等效的 C++ 代码时,我可以使用objdump轻松做到这一点:swimrun

 663     f->run(1);
 664  8c3:   48 8b 45 e0             mov    -0x20(%rbp),%rax
 665  8c7:   48 8b 00                mov    (%rax),%rax
 666  8ca:   48 83 c0 08             add    $0x8,%rax <------ offset 8 for father::run

继承类的相同偏移量:

 689     s->run(3);
 690  914:   48 8b 45 e8             mov    -0x18(%rbp),%rax
 691  918:   48 8b 00                mov    (%rax),%rax
 692  91b:   48 83 c0 08             add    $0x8,%rax <------ offset 8 for son::run

当我使用Apache 的类实用程序时,我得到了一些接近的东西(池常量)

1)CONSTANT_Methodref[10](class_index = 11, name_and_type_index = 20)
2)CONSTANT_Class[7](name_index = 21)
3)CONSTANT_Methodref[10](class_index = 2, name_and_type_index = 20)
4)CONSTANT_Methodref[10](class_index = 2, name_and_type_index = 22)
5)CONSTANT_Methodref[10](class_index = 2, name_and_type_index = 23)
6)CONSTANT_Class[7](name_index = 24)
7)CONSTANT_Methodref[10](class_index = 6, name_and_type_index = 20)
8)CONSTANT_Methodref[10](class_index = 6, name_and_type_index = 22)
9)CONSTANT_Methodref[10](class_index = 6, name_and_type_index = 23)
10)CONSTANT_Class[7](name_index = 16)
11)CONSTANT_Class[7](name_index = 25)
12)CONSTANT_Utf8[1]("<init>")
13)CONSTANT_Utf8[1]("()V")
14)CONSTANT_Utf8[1]("Code")
15)CONSTANT_Utf8[1]("LineNumberTable")
16)CONSTANT_Utf8[1]("main")
17)CONSTANT_Utf8[1]("([Ljava/lang/String;)V")
18)CONSTANT_Utf8[1]("SourceFile")
19)CONSTANT_Utf8[1]("main.java")
20)CONSTANT_NameAndType[12](name_index = 12, signature_index = 13)
21)CONSTANT_Utf8[1]("Father")
22)CONSTANT_NameAndType[12](name_index = 26, signature_index = 27)
23)CONSTANT_NameAndType[12](name_index = 28, signature_index = 27)
24)CONSTANT_Utf8[1]("Son")
25)CONSTANT_Utf8[1]("java/lang/Object")
26)CONSTANT_Utf8[1]("run")
27)CONSTANT_Utf8[1]("(I)V")
28)CONSTANT_Utf8[1]("swim")

似乎虚函数表的条目是基于名称的(“run”、“swim”)。真的是这样吗?为了完成,这里是提取常量池的 Java 脚本。

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.ConstantPool;

class test
{
    public static void main(String[] args)
    {
        try
        {
            ClassParser c = new ClassParser(args[0]);
            JavaClass j = c.parse();
            ConstantPool cp = j.getConstantPool();
            System.out.format(cp.toString());
        }
        catch (Exception e) { return; }
    }
}
4

1 回答 1

3

来自 Lalith Suresh [3] 的帖子

在字节码级别,invokevirtual 操作触发了 Java 等效的 C++ 虚拟方法调用 [1]。根据规范,该操作接受一个索引(实际上是两个,但它被组合以获得一个值),该索引用于获取对方法的引用和对要在其中找到该方法的类的引用。在 HotSpot [2] 中,这对应于目标类中的 vtable 索引,用于解析可以调用的预期目标方法。根据类层次结构,每个类可能有不同大小的 vtable,覆盖父方法的子类可以重用 vtable。

但是,在 JIT 编译的代码中,通常不使用 vtable 调用序列。HotSpot 执行内联缓存,其中代码乐观地假设虚拟方法的目标始终指向相同的实现(意味着调用站点是单态的)。在这种情况下,在代码中插入一个检查来验证假设是否真的成立(即目标没有改变),然后将控制直接传递给目标方法,而不涉及任何 vtable 查找,类似于如何所谓的“未链接”呼叫站点有效。简单地说,如果这个假设失败的次数足够多,代码就会退回到冗长的 vtable 查找方法。

[1] https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokevirtual

[2] https://wikis.oracle.com/display/HotSpotInternals/VirtualCalls

[3] https://www.quora.com/How-is-the-virtual-method-table-implemented-in-Java

于 2021-10-07T15:27:25.820 回答