In this subsection, we present a virtual table layout that eliminates the overhead of interface method lookup over normal virtual method lookup.
This enhancement addresses a problem raised by multiple inheritance of interfaces in Java. The virtual machine instruction set contains an invokeinterface instruction, used to invoke interface methods. A common technique to implement this instruction is to prepare multiple virtual tables for each class: a main virtual table used for normal virtual method invocation, and one additional virtual table for each interface directly or indirectly implemented by the class[5]. Each method declared in an interface is given an index within its virtual table. After preparation, each invokeinterface has two arguments: an interface number, and a method index. On execution, the invokeinterface instruction operates its method lookup in two steps. It first lookups up the appropriate virtual table (using linear, binary, or hashed search), then it retrieves the method pointer in a single operation from the virtual table entry located at the given method index. This interface lookup procedure has the following overhead over normal virtual method lookup: it needs to do a search to find the appropriate virtual table. It would be possible to implement a constant time lookup using compact encoding[31], but unfortunately, dynamic class loading requires updating this information dynamically, which is difficult to do in a multi-threaded Java environment. Our approach is simple, and does not require dynamic recomputation of tables or code rewrite.
The idea of maintaining multiple virtual tables in case of multiple inheritance
is reminiscent of C++ implementations[19]. But, Java's multiple
inheritance has a major
semantic difference: it only applies to interfaces which may only declare method
signatures without providing an implementation.
Furthermore, if a Java class
implements two distinct
interfaces which declare the same method signature, this class satisfies both
interfaces by providing a single implementation of this method. C++ allows the
inheritance of distinct implementations of the same method signature.
We take advantage of this important difference to rethink the appropriate data structure needed for efficient interface method lookup. Our ideas originate from previous work on efficient method lookup in dynamically typed OO languages using of selector-indexed dispatch tables[12,18,30]. We assign a globally unique increasing index8 to each method signature declared in an interface. A method signature declared in multiple interfaces has a single index. When the virtual table of a class is created, we also create an interface virtual table that grows down from the normal virtual table. This interface virtual table has a size equal to the highest index of all methods declared in the direct and indirect super interfaces of the class. For every declared super interface method, the entry at its index is filled with the address of its implementation. Interface invocation is then encoded with the invokeinterface instruction, and a single interface method index. The execution of invokeinterface can then proceed at the exact same cost as an invokevirtual.
The interface virtual table is a sparse array of
method pointers. As more
interfaces are loaded, with many interface method signatures, the amount of
free space in interface virtual tables grows. The traditional approach has been
to use table compression techniques to reduce the amount of free space. However,
these techniques are poorly adapted to concurrent and dynamic class loading
environments like the Java virtual machine, as they require dynamic
recompilation.
Our approach differs. Instead of compressing interface virtual tables, we simply return the free space in them to the related class loader memory manager (see section 5.4). This memory is then used to store all kinds of other class loader related data structures. In other words, we simply recycle the free space of sparse interface virtual tables within a class loader. The layout of interface virtual tables is illustrated in Figure 5.
As interface usage in most Java programs range from very low to moderate, we could argue that it is unlikely that the free space returned by interface virtual tables will grow faster than the rate at which it is recycled. However, in order to handle pathological cases, we also provide a very simple technique, which incurs no runtime overhead, to limit the maximal growth of interface virtual tables. To limit this growth to entries, we stop allocating new interface method indices as soon as index is given. Then, new interface method signatures are encoded using traditional techniques. The trick to make this work is to encode interface calls differently, based on whether the invoked method signature has been assigned an index or not. The traditional technique used to handle overflow can safely ignore all interface methods which have already been assigned an index.