vtable and itable — Junior¶
What? A vtable (virtual method table) is a per-class array of method pointers the JVM uses to resolve
invokevirtualcalls. An itable (interface method table) is a per-interface lookup table used byinvokeinterface. Both live inside HotSpot'sKlassmetadata in metaspace. Together they are the runtime machinery that turnsanimal.speak()into "call the rightspeakfor whichever concrete classanimalactually is". How? Each class loaded by the JVM gets aKlassstructure. TheKlasscontains, among other things, a vtable (one slot per overridable method, including inherited ones) and an itable for each interface the class implements.invokevirtualbecomes a fixed-offset array load:vtable[slot].invokeinterfaceis a search into the right itable, accelerated by an inline cache at the call site.
1. The point of vtables and itables in one paragraph¶
When you write shape.area(), the JVM doesn't decide at compile time which area() runs — it depends on the actual class of shape at runtime. To make that decision fast, HotSpot doesn't search through the class's methods on every call. Instead, it builds a small array of method pointers per class — the vtable — and resolves area() to a fixed index into that array. At runtime, dispatch is just two memory loads: load the object's class pointer, then load the method at the precomputed slot. Itables do the same job for interface calls, with one extra step because a class may implement many interfaces.
This is the same idea C++ compilers use for virtual methods. The difference is that in Java the table is built by the JVM during class loading, not by the compiler at link time.
See ../01-jvm-method-dispatch/ for the bytecode-level picture and ../04-object-memory-layout/ for how the Klass pointer sits in every object's header.
2. A simple vtable example — Animal hierarchy¶
class Animal {
public void speak() { System.out.println("..."); }
public void sleep() { System.out.println("zzz"); }
}
class Dog extends Animal {
@Override public void speak() { System.out.println("woof"); }
}
class Cat extends Animal {
@Override public void speak() { System.out.println("meow"); }
}
Conceptually, after class loading, the vtables look like:
Animal.vtable [0]=Animal.speak [1]=Animal.sleep
Dog.vtable [0]=Dog.speak [1]=Animal.sleep (inherited at same slot)
Cat.vtable [0]=Cat.speak [1]=Animal.sleep
The crucial property: speak lives at slot 0 in every class in the hierarchy. When you call animal.speak() and animal happens to be a Dog, the JVM doesn't search — it loads Dog's vtable and reads slot 0. The slot is decided once, when Animal is loaded; every subclass inherits the layout.
sleep is not overridden, so Dog.vtable[1] and Cat.vtable[1] both point back at Animal.sleep. The slot exists in every subclass even if it isn't overridden, because the parent must work too — code that has a Animal a = new Dog() and calls a.sleep() reads slot 1, and slot 1 had better contain something.
3. An everyday analogy — the elevator panel¶
Imagine an office building with floors 1 to 10. The elevator panel has a button for each floor. You don't "search" for floor 7 — you press the button at the known position, and the elevator goes. Now imagine a renovation adds a new wing on floors 5 and 6; the existing buttons keep working because the panel layout didn't change. New buttons (e.g., a "spa" button on floor 11) are appended.
That's the vtable. Existing slots are stable across inheritance; subclasses can override what a slot does (the destination behind the button), and new methods declared in subclasses get new slots appended.
4. itables — when interfaces enter the picture¶
An interface is not a class — it doesn't have a fixed slot in your class's vtable, because a class can implement many interfaces (ArrayList implements List, RandomAccess, Cloneable, Serializable). Each interface has its own table of methods, and each class implementing the interface keeps a per-interface mapping from "interface method" to "class method".
interface Greeter { void greet(); }
interface Sweeper { void sweep(); }
class Robot implements Greeter, Sweeper {
public void greet() { System.out.println("hello"); }
public void sweep() { System.out.println("brush brush"); }
}
Conceptually:
Robot has two itables:
itable for Greeter: [0]=Robot.greet
itable for Sweeper: [0]=Robot.sweep
And the regular vtable contains Robot.greet and Robot.sweep too,
because they are concrete instance methods on Robot.
When you call ((Greeter) robot).greet(), the JVM:
- Looks up which itable belongs to
Greeter(a small per-class search). - Reads slot 0 of that itable.
- Calls the resulting method.
That's slower than a vtable call. HotSpot solves this by caching the result at the call site (an inline cache) so the second call onward is almost as fast as invokevirtual. We will see that in middle.md.
5. What lives in vtables vs not¶
Not every method goes into the vtable. The rule is "only methods that can actually be overridden". HotSpot uses static dispatch (no vtable) for:
privatemethods — no subclass can see them, so there's nothing to override.staticmethods — they belong to the class, not the instance; resolved at link time.finalmethods — declared non-overridable.- Constructors — never inherited.
- Methods on a
finalclass — the whole class is closed.
Common newcomer question: "Where do private methods go?" They live in the class's method table (the Klass has all methods, public and private), but they are not given vtable slots. invokeprivate-style calls (actually invokespecial in bytecode) go straight to the method address — no array lookup. Same for invokestatic and constructor invokespecial.
class Calculator {
public int add(int a, int b) { return a + b; } // vtable slot
private int doubleIt(int x) { return x * 2; } // NOT in vtable
static int zero() { return 0; } // NOT in vtable
public final int answer() { return 42; } // NOT in vtable
}
6. Why this matters even at junior level¶
You almost never look at vtables directly. You don't need to. But a mental model of vtables explains things that otherwise look like magic:
- Why dispatch is fast despite Java being "polymorphic by default".
- Why overriding
equalsworks even when the variable is typedObject. - Why marking a method
finalis sometimes a real micro-optimization (no vtable indirection). - Why deep inheritance hierarchies have a class-loading cost (each subclass builds a full vtable copy).
- Why a class with twenty interfaces costs more at startup than a class with one.
When a coworker says "the JIT will devirtualize this", they're saying "the JIT can prove the vtable slot has only one possible target and can call it directly, skipping the array load". That conversation is unintelligible without the vtable picture.
7. Common newcomer confusions¶
Confusion 1: "Does an @Override method get a new slot?"
No. It reuses the parent's slot. That's the whole point: slot N means "method N" across the hierarchy, and overriding means "replace what's at slot N for this class". A new method declared in the subclass (not overriding anything) gets an appended slot.
Confusion 2: "Where does Object.toString() sit?"
In a fixed slot near the start of every class's vtable, inherited from Object. That's why myObj.toString() works on every Java object — the slot is reserved in Object.vtable and inherited by every class.
Confusion 3: "Are vtables in the object?"
No — the object header only contains a pointer to the Klass (its class metadata), and the Klass holds the vtable. Putting the vtable in every object would waste memory.
Confusion 4: "Records — do they have vtables?"
Records are implicitly final. Their methods still go into a vtable for Record and Object inherited entries, but no subclass will ever override them, so the JIT freely devirtualizes.
Confusion 5: "Lambdas and method references?"
Each lambda is a tiny class implementing a functional interface. It has its own vtable + an itable for the functional interface. The JIT inlines them aggressively, but the underlying machinery is the same as any other class.
8. Quick rules¶
- vtable = per-class array of method pointers; one slot per overridable method.
- itable = per-interface table; one per interface a class implements.
-
invokevirtualis an array load at a fixed slot;invokeinterfaceis a slower lookup, cached at the call site. -
private,static,final, and constructor methods do NOT go in the vtable. - Subclasses share the parent's vtable layout — overriding replaces a slot, never appends to it.
- Each
Klassin metaspace owns its vtable and itables; the object header just points to theKlass.
9. What's next¶
| Topic | File |
|---|---|
| Vtable layout across deeper hierarchies, itable mechanics | middle.md |
| Class loading, secondary super check, bridge methods | senior.md |
| Mentoring, tooling, ArchUnit guardrails | professional.md |
| JVMS section references, HotSpot source pointers | specification.md |
| Buggy dispatch patterns and how they surface | find-bug.md |
| Cost models, devirtualization, benchmarking | optimize.md |
| Hands-on HSDB + JOL + JMH exercises | tasks.md |
| Interview Q&A | interview.md |
Memorize this: the vtable is "an array of method pointers per class, indexed by slot"; the itable is "the same idea per interface, with a lookup step on top". invokevirtual is two cache-line loads; invokeinterface is a few more plus a cached lookup. Every other detail in this section is a refinement of those two sentences.