Java接口在内部是如何实现的?(虚函数表?)

43

C++有多重继承。在汇编级别上,多重继承的实现可能非常复杂,但是网上有很好的描述,说明通常如何完成这个过程(vtables、指针修复、thunks等)。

Java没有多重实现继承,但它确实有多重接口继承,因此我认为一个类只有一个vtable的简单实现无法实现这一点。Java内部如何实现接口呢?

我意识到,与C++相反,Java是Jit编译的,因此不同的代码片段可能会被不同地优化,并且不同的JVM可能会以不同的方式处理事情。那么,是否有许多JVM遵循的一般策略?或者有人知道特定JVM的实现吗?

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

(供参考:这个问题类似于关于CLR的问题,这篇MSDN文章中似乎有一个很好的解释,不过可能已经过时了。我没有找到任何类似的Java资料。) 编辑:
  • 我的意思是“implements”是指“GCC编译器如何实现整数加法/函数调用/等”,而不是“Java类ArrayList实现了List接口”的意思。
  • 我知道这在JVM字节码级别上是如何工作的,我想知道的是在加载类文件并编译字节码后JVM生成什么样的代码和数据结构。

2
你提到了接口继承和实现继承。实现继承比较困难,因为你需要有一个定义好的搜索顺序。接口继承更简单。你只需要一个包含所有需要被实现的方法签名映射表,无需搜索顺序(因为它没有任何实现)。在那里没有顺序。 - extraneon
1个回答

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

编辑Hotspot Wiki有关于vtable和itable stubs的更多细节。在多态情况下,它仍然将内联缓存版本放入调用站点。但是,该代码实际上是一个存根,用于在vtable或itable中进行查找。对于每个vtable偏移量(0、1、2、...),都有一个vtable存根。接口调用在查找给定偏移量的itable之前,会在itable数组上添加一个线性搜索。


那些关于VirtualCalls和InterfaceCalls的Hotspot Wiki页面似乎是我正在寻找的内容。不过还需要抽出时间来阅读所有内容。 - JanKanis
1
Wiki已经迁移到https://wikis.oracle.com/display/HotSpotInternals/Home - Ted Hopp
Oracle的维基已经被冻结了,我认为https://wiki.openjdk.java.net/display/HotSpot/Main是可更新信息的最终去处。 - JanKanis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接