如何查看继承Java类的虚函数表



我有以下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);
}
}

我在虚拟函数表中寻找swimrun实际偏移。当我使用等效的c++代码时,我可以使用objdump:

轻松地完成
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; }
}
}

Lalith Suresh的帖子[3]

在字节码级别,调用虚拟操作是触发的Java相当于c++的虚方法调用[1]。根据规范,操作接受一个索引(实际上是两个,但它是组合以获得单个值),用于获取对a的引用方法和对该方法所在的类的引用发现。在HotSpot[2]中,这对应于目标类,解析预期的目标方法可以是哪个调用。根据不同的类层次结构,每一个类可能有一个大小不同的虚变量表,以及覆盖类方法的子类父类可以重用虚表

然而,在JIT编译的代码中,虚函数表调用顺序为通常不使用。HotSpot执行内联缓存,其中代码乐观地假设一个虚方法的目标总是指向相同的实现(意味着调用站点是)单型的)。在这种情况下,检查被插入到代码中验证假设是否确实成立(即目标)未更改),然后将控制权直接传递给目标方法而不涉及任何虚值表查找,类似于所谓的"非链接"呼叫站点工作。简单地说,如果这个假设不成立很多时候,代码会退回到冗长的虚函数表查找方法。

[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

最新更新