java接口内部是如何实现的? (虚函数表?)
C++具有多重继承。在程序集级别实现多重继承可能相当复杂,但是网上有很好的描述这通常是如何完成的(虚函数表、指针修复、thunk 等)。
Java 没有多个实现继承,但它确实有多个接口继承,因此我认为每个类只有一个 vtable 的直接实现无法实现这一点。 java内部是如何实现接口的?
我意识到与 C++ 相反,Java 是 Jit 编译的,因此不同的代码片段可能会进行不同的优化,并且不同的 JVM 可能会做不同的事情。那么,是否有许多 JVM 遵循的一些通用策略,或者有人知道特定 JVM 中的实现吗?
此外,JVM 经常进行去虚拟化和内联方法调用,在这种情况下,根本不涉及 vtable 或等效项,因此询问实现虚拟/接口方法调用的实际汇编序列可能没有意义,但我假设大多数 JVM 仍然保留一些如果无法将所有内容去虚拟化,则可以使用类的一般表示形式。这个假设是错误的吗?这个表示看起来像 C++ vtable 吗?如果是这样,接口是否有单独的 vtable,它们如何与类 vtable 链接?如果是这样,对象实例是否可以像 C++ 中的对象实例一样拥有多个 vtable 指针(指向类/接口 vtable)?类类型和接口类型对同一对象的引用是否始终具有相同的二进制值,或者它们是否可以像 C++ 中那样不同,需要指针修复?
(供参考:这个问题询问了一些关于 CLR 的类似问题,这篇 msdn 文章 中似乎有一个很好的解释尽管现在我还没有找到类似的 Java 内容。)
编辑:
- 我的意思是“GCC 编译器如何实现整数加法/”。函数调用/etc”,而不是“Java类ArrayList实现List接口”的意义。
- 我知道它在 JVM 字节码级别是如何工作的,我想知道的是 JVM 在加载类文件和编译字节码后会生成什么样的代码和数据结构。
C++ has multiple inheritance. The implementation of multiple inheritance at the assembly level can be quite complicated, but there are good descriptions online on how this is normally done (vtables, pointer fixups, thunks, etc).
Java doesn't have multiple implementation inheritance, but it does have multiple interface inheritance, so I don't think a straight forward implementation with a single vtable per class can implement that. How does java implement interfaces internally?
I realize that contrary to C++, Java is Jit compiled, so different pieces of code might be optimized differently, and different JVMs might do things differently. So, is there some general strategy that many JVMs follow on this, or does anyone know the implementation in a specific JVM?
Also JVMs often devirtualize and inline method calls in which case there are no vtables or equivalent involved at all, so it might not make sense to ask about actual assembly sequences that implement virtual/interface method calls, but I assume that most JVMs still keep some kind of general representation of classes around to use if they haven't been able to devirtualize everything. Is this assumption wrong? Does this representation look in any way like a C++ vtable? If so do interfaces have separate vtables and how are these linked with class vtables? If so can object instances have multiple vtable pointers (to class/interface vtables) like object instances in C++ can? Do references of a class type and an interface type to the same object always have the same binary value or can these differ like in C++ where they require pointer fixups?
(for reference: this question asks something similar about the CLR, and there appears to be a good explanation in this msdn article though that may be outdated by now. I haven't been able to find anything similar for Java.)
Edit:
- I mean 'implements' in the sense of "How does the GCC compiler implement integer addition / function calls / etc", not in the sense of "Java class ArrayList implements the List interface".
- I am aware of how this works at the JVM bytecode level, what I want to know is what kind of code and datastructures are generated by the JVM after it is done loading the class files and compiling the bytecode.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
HotSpot JVM 的关键功能是内联缓存。
这实际上并不意味着目标方法是内联的,而是意味着假设
被放入 JIT 代码中,以后对虚拟或接口方法的每次调用都将针对该代码
完全相同的实现(即调用站点是单态的)。在这种情况下,一个
检查被编译到机器代码中的假设是否确实成立(即是否
目标对象的类型与上次相同),然后转移控制权
直接到目标方法 - 根本不涉及虚拟表。如果断言失败,则可以尝试将其转换为超态调用站点(即具有多种可能的类型);如果这也失败(或者如果这是第一次调用),则使用 vtable(对于虚拟方法)和 itable(对于接口)执行常规的冗长查找。
编辑:Hotspot Wiki 有关于 vtable 和 itable 存根的更多详细信息。在多态情况下,它仍然将内联缓存版本放入调用站点中。然而,该代码实际上是一个在 vtable 或 itable 中执行查找的存根。每个 vtable 偏移量(0、1、2、...)都有一个 vtable 存根。 接口调用 在查看 itable 之前添加对 itable 数组的线性搜索(如果找到的话)在给定的偏移量处。
The key feature of the HotSpot JVM is inline caching.
This doesn't actually mean that the target method is inlined, but means that an assumption
is put into the JIT code that every future call to the virtual or interface method will target
the very same implementation (i.e. that the call site is monomorphic). In this case, a
check is compiled into the machine code whether the assumption actually holds (i.e. whether
the type of the target object is the same as it was last time), and then transfer control
directly to the target method - with no virtual tables involved at all. If the assertion fails, an attempt may be made to convert this to a megamorphic call site (i.e. with multiple possible types); if this also fails (or if it is the first call), a regular long-winded lookup is performed, using vtables (for virtual methods) and itables (for interfaces).
Edit: The Hotspot Wiki has more details on the vtable and itable stubs. In the polymorphic case, it still puts an inline cache version into the call site. However, the code actually is a stub that performs a lookup in a vtable, or an itable. There is one vtable stub for each vtable offset (0, 1, 2, ...). Interface calls add a linear search over an array of itables before looking into the itable (if found) at the given offset.