Java接口是如何在内部实现的?(vtables?

C++具有多重继承。在程序集级别实现多重继承可能相当复杂,但网上有很好的描述,说明通常如何完成此操作(vtables,指针修复,thunks等)。

Java没有多个实现继承,但它确实有多个接口继承,所以我不认为每个类只有一个vtable的直接实现可以实现这一点。Java如何在内部实现接口?

我意识到,与C++相反,Java是Jit编译的,因此不同的代码片段可能会以不同的方式进行优化,不同的JVM可能会以不同的方式做事。那么,是否有一些通用策略可供许多JVM遵循,或者有人知道特定JVM中的实现?

此外,JVM经常去虚拟化和内联方法调用,在这种情况下,根本没有涉及vtables或等效项,因此询问实现虚拟/接口方法调用的实际汇编序列可能没有意义,但我假设大多数JVM仍然保留某种类的一般表示形式,如果他们无法去虚拟化所有内容。这个假设是错误的吗?这种表示形式看起来是否像C++ vtable?如果是这样,接口是否有单独的 vtable,这些 vtable 如何与类 vtable 链接?如果是这样,对象实例是否可以像C++中的对象实例那样具有多个 vtable 指针(指向类/接口 vtables)?类类型和接口类型对同一对象的引用是否始终具有相同的二进制值,或者它们是否可以像C++那样不同,因为它们需要指针修正?

(参考:这个问题问了关于CLR的类似问题,在这篇msdn文章中似乎有一个很好的解释,尽管现在可能已经过时了。我无法为Java找到类似的东西。

编辑:

  • 我的意思是“实现”意义上的“GCC编译器如何实现整数加法/函数调用/等”,而不是“Java类ArrayList实现List接口”的意义上。
  • 我知道这在JVM字节码级别是如何工作的,我想知道的是JVM在完成加载类文件和编译字节码后生成了什么样的代码和数据结构。

答案 1

HotSpot JVM 的主要特性是内联缓存。这实际上并不意味着目标方法是内联的,而是意味着在JIT代码中放入一个假设,即将来对虚拟或接口方法的每次调用都将针对完全相同的实现(即调用站点是单态的)。在这种情况下,将检查假设是否实际成立(即目标对象的类型是否与上次相同),然后将控制权直接转移到目标方法中 - 根本不涉及虚拟表。如果断言失败,可以尝试将其转换为巨态调用站点(即具有多种可能的类型);如果这也失败了(或者如果是第一次调用),则使用vtables(对于虚拟方法)和itables(对于接口)执行常规的冗长查找。

编辑Hotspot Wiki有更多关于可备份和可迭代存根的详细信息。在多态情况下,它仍然将内联缓存版本放入调用站点。但是,代码实际上是在 vtable 或 itable 中执行查找的存根。每个 vtable 偏移量(0, 1, 2, ...)都有一个 vtable 存根。接口调用在给定偏移处查看可迭代对象(如果找到)之前,在可迭代对象数组上添加线性搜索。


答案 2