Neither.
Inheritance is a programming language concept, not a real action. When you investigate the compiled Child
class, e.g. with javap
, you won’t find any artifact related to the three methods of Parent
. There will be the information that Child
has the superclass Parent
, but no mentioning of the inherited methods, neither as references nor as copies.
The fact that Child
conceptionally inherits methods from Parent
comes into play when you actually try to invoke one of them through a variable whose compile-time type is Child
, like in your Test
class. Then it is up to the compiler to find the inherited methods in order to compile the code containing the invocation correctly. It is also up to the compiler whether it searches the class hierarchy every time it resolves a target method of an invocation or whether it collects the existing methods in certain data structures to speed up subsequent lookups and which data structures it uses.
This process is more complicated than you might think. There may be multiple candidates among which the compiler has to select one and it may even fail due to ambiguity. The result of the process will be a decision about whether the invocation is valid and, in case it is valid, a single target method and its actual signature.
The compiled invocation will contain the name and signature of the target method and a reference to the compile time type of the reference on which it is invoked, i.e. Child
. It will not contain the information that the actual method has been inherited from Parent
. This is intentional. Whether Child
declares the method itself, inherits it from Parent
or overrides a method of Parent
should not affect the code of the invoker Test
nor the compatibility between compiled class files.
This implies that at runtime a class like Test
may contain a reference, given by name and signature, to a method in Child
not being stored in the class file of Child
, in other words, the JVM is responsible as well, for making the concept of inheritance working. At the same time, it has to ensure that the concept of overriding works, when a method invocation is executed.
The way it implements this is also unspecified, leaving room for different strategies. Searching class hierarchies on every invocation would be legal, but lead to poor performance.
A common technique is a vtable. Associated with every class is a table (array) containing references to the available methods, be it declared or inherited. On initialization of a subclass, its table will start with a copy of the superclass’ table, having entries for new methods appended at the end and entries of overridden methods changed. At some time, the method invocation will be linked by finding the index of the vtable entry, appropriate to the specified name and signature. A typical strategy is to do the lookup on the first invocation. The instruction is then modified to refer to the index to avoid subsequent lookups. From then, subsequent executions consist of fetching the vtable for an object’s runtime class and getting the method reference from the table’s entry.
Considering that, you could say that at runtime, inheritance is typically implemented as some kind of reference—but wait.
Implementations like Oracle’s JVM are capable of doing hotspot optimizations in which the context of often executed code is considered. There might be, for example an inherited method which itself invokes methods which have been overridden in some subclasses. When the JVM finds out that this method is called very often on a single concrete subclass, it may create an optimized version for that particular case. Then, a copy of the method’s code will be the starting point for the subsequent code transformations, inlining code of the specific subclass, etc.
Since such sophisticated implementations will use references for non-optimized code while using optimized copies in other cases, an alternative to the initial answer answer could be:
Both.