Inheritance — Professional¶
What? The bytecode-level mechanics of
extends,super, andinvokevirtual/invokespecial/invokeinterface; vtable construction at class-link time; the JLS rules on subtype compatibility, covariance/contravariance/invariance, and erasure interactions; the design rationale behind sealed classes and JEP 412 pattern matching. How? By reading the spec and confirming withjavap,-XX:+PrintAssembly, and JFR class-load events.
1. The extends keyword in bytecode¶
A class file carries a single super_class field (constant pool index → CONSTANT_Class):
If absent, the implicit super is java/lang/Object. There's no "extends" instruction at runtime — the relationship is encoded in metadata, used by the loader/linker.
2. Class loading, linking, and inheritance¶
JVMS §5.3 specifies that loading MyClass triggers loading of its superclass:
If
Cis not an array class, recursively load its direct superclass and direct superinterfaces using the same class loader.
Linking (verification + preparation + resolution) happens after loading. Vtable layout is determined during the preparation phase: each class's method table is built by:
- Copying the parent's vtable.
- For each method declared by
MyClass, either: - Add a new slot (if no parent method has the same signature), or
- Override an existing slot (if it does).
Verification ensures the override is type-compatible (return covariance, throws narrowing, etc.).
3. invokevirtual semantics¶
JVMS §6.5.invokevirtual:
The objectref must be of type
reference. The runtime constant pool item at that index must be a symbolic reference to a method or interface method, which gives the name and descriptor of the method as well as a symbolic reference to the class in which the method is to be found.
The JVM resolves the method symbolic reference to a vtable index during preparation/resolution. Then the call dispatches:
1. Resolve method ref → vtable slot N.
2. Pop objectref from stack.
3. Read objectref's klass pointer from header.
4. Index klass's vtable at slot N.
5. Push args, jump to method.
Bytecode example:
class Animal { void speak() {} }
class Dog extends Animal { @Override void speak() {} }
void test(Animal a) { a.speak(); }
The reference is to Animal.speak, but at runtime, if a is a Dog, dispatch lands in Dog.speak.
4. invokespecial for super calls¶
When you write super.m(), the bytecode emits invokespecial, not invokevirtual. invokespecial performs no vtable lookup — it calls the method directly as resolved at link time.
class Dog extends Animal {
@Override
void speak() {
super.speak(); // invokespecial Animal.speak
// ...
}
}
Same instruction is used for: - super.method(...) calls - Constructor calls (<init>) - Private method calls (in some bytecodes, also invokevirtual since Java 11+)
JVMS §6.5.invokespecial enforces: the method must be in the current class, an ancestor, or Object.
5. invokeinterface semantics¶
Interface method dispatch is more complex than virtual dispatch because: - A class can implement many interfaces. - An interface's method slot in the receiver's itable depends on which interface.
JVMS §6.5.invokeinterface:
1. Resolve interface method ref to (interface, method).
2. Pop objectref.
3. Find the interface's itable in objectref's klass.
4. Look up the method.
5. Invoke.
Modern HotSpot caches the resolved (klass, method) pair in an inline cache. After the first call, dispatch is essentially a klass-pointer compare + direct call.
6. JLS subtype rules¶
JLS §4.10 defines the subtype relation <: :
- Reflexive:
T <: T. - Class/interface: if
B extends A, thenB <: A. - Array covariance:
B[] <: A[]ifB <: A(this is the famously unsound rule that enablesArrayStoreException). - Generics: invariant —
List<B>is not a subtype ofList<A>even ifB <: A. Wildcards (? extends,? super) provide use-site variance. - Primitives: chain like
byte <: short <: int <: long, etc., for widening conversions — not strict subtyping.
null is a subtype of every reference type.
7. Covariant return types¶
JLS §8.4.5: an override may declare a return type that is a subtype of the parent's return type.
class A { Object clone() { return new Object(); } }
class B extends A { @Override B clone() { return new B(); } }
Bytecode reveals what's going on:
B.clone()
Code:
...
areturn // returns reference of type B
// Synthesized "bridge method" for the parent's signature:
Object clone() {
return clone(); // calls the B-returning override and returns it as Object
}
The B clone() method gets a synthetic bridge method with the parent's exact signature, which delegates to the actual override. This preserves binary compatibility.
8. Generics and bridge methods¶
Bridge methods are also why erasure and inheritance interact strangely.
class Box<T> { void put(T x) { } }
class IntBox extends Box<Integer> {
@Override void put(Integer x) { }
}
Box<T> erases to Box, so Box.put has signature put(Object). IntBox.put(Integer) doesn't match put(Object) directly — bytecode-wise, they'd be different methods. The compiler synthesizes a bridge:
IntBox:
put(Integer) { /* user code */ }
put(Object) { /* synthetic bridge */
checkcast Integer
invokevirtual put(Integer)
}
The bridge ensures dispatch works regardless of whether the caller invokes through Box or IntBox.
9. Interface default methods and inheritance¶
Java 8 introduced default methods on interfaces. Resolution rules (JLS §9.4.1.3):
- Class wins over interface. If a class declares or inherits the method from a class chain, that wins.
- More specific interface wins. If two interfaces both provide defaults, the one further down the inheritance chain wins.
- Otherwise, the implementer must override to disambiguate.
Bytecode-wise, default methods are stored on the interface's class file and dispatched via invokevirtual (Java 8+) or invokeinterface, with a slot in the implementer's itable.
10. Sealed types in bytecode¶
A sealed class has PermittedSubclasses attribute in its class file (JVMS §4.7.31, added in Java 17):
PermittedSubclasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
u2 classes[number_of_classes];
}
The verifier enforces: a class with a super_class of a sealed class must be in that sealed class's PermittedSubclasses list.
$ javap -v Shape.class | grep -A 4 PermittedSubclasses
PermittedSubclasses:
#14 // class Circle
#15 // class Square
#16 // class Triangle
11. Pattern matching switch and bytecode¶
Java 21's pattern-matching switch compiles to a switch over class hashes plus instanceof chains, often using invokedynamic and SwitchBootstraps:
The bootstrap method generates a runtime classifier that returns the matched index. Each case body is a label.
This indirection allows sealed-type exhaustiveness checking, guards (when), and binding all in a single switch.
12. The Method Resolution algorithm¶
JLS §15.12 describes overload resolution at compile time:
- Find all applicable methods (signatures that can accept the argument types).
- Among applicable, find the most specific (no other applicable method is more specific).
- If exactly one — that's the chosen method.
- If ambiguous — compile error.
Specificity rules: - Subtype relations on parameters. - Method type parameter substitutions. - Three phases: 1. Strict invocation: only direct subtypes, no boxing/varargs. 2. Loose invocation: allow boxing/unboxing. 3. Variable-arity: allow varargs.
A method found in phase 1 always wins over phase 2/3, even if "less specific" by other measures.
13. Fields vs methods at the bytecode level¶
| Operation | Bytecode | Resolved at | Polymorphic? |
|---|---|---|---|
| Read instance field | getfield | Static type | No |
| Write instance field | putfield | Static type | No |
| Read static field | getstatic | Static type | No |
| Write static field | putstatic | Static type | No |
| Call instance method | invokevirtual | Receiver | Yes |
| Call interface method | invokeinterface | Receiver | Yes |
| Call static method | invokestatic | Static type | No |
| Call constructor | invokespecial | Static type | No |
| Call super method | invokespecial | Static type | No |
Field access is always static (declared type wins). Method invocation is virtual unless one of the special opcodes applies.
14. Class hierarchy in HotSpot internals¶
In HotSpot, every class has a Klass C++ object containing: - Pointer to its superclass Klass. - Method tables (vtable, itable). - Field offsets, packed for alignment. - Constant pool, name, attributes.
Subclass Klass objects link to parent via _super. Vtable is built greedily — child copies parent's table, then patches overridden slots.
For research, see oop.hpp, klass.hpp, instanceKlass.hpp in the OpenJDK source.
15. Where to look in the spec¶
| Topic | Spec |
|---|---|
Class declaration / extends | JLS §8.1.4 |
| Override compatibility | JLS §8.4.8 |
| Subtyping rules | JLS §4.10 |
| Inheritance for fields/methods | JLS §8.2, §8.4.8 |
| Default methods resolution | JLS §9.4.1 |
| Sealed classes | JLS §8.1.1.2 |
invokevirtual / invokespecial | JVMS §6.5 |
| Class loading / linking | JVMS §5 |
PermittedSubclasses attribute | JVMS §4.7.31 |
| Vtable & method resolution | JVMS §5.4.5 |
| Method overload resolution | JLS §15.12 |
Memorize this: at the bytecode level, extends is a single constant-pool reference; vtables are built at link time; invokevirtual is a vtable lookup; invokespecial is a direct call. Sealed types add a PermittedSubclasses attribute used by the verifier and the pattern-matching compiler. Generics + inheritance produce bridge methods. Java's binary compatibility rules are the contract that lets the JVM optimize all of this.