the five invoke instructions
invokestatic, virtual, interface, special, dynamic. who calls what.
- chapter
- chapter 13.04
- reading time
- 8 min read
every method call in java compiles to one of five instructions. invokestatic (no receiver), invokespecial (constructors and super. calls, exact target), invokevirtual (pick the method by the object's class, through a vtable), invokeinterface (same, through an interface type) and invokedynamic (ask a bootstrap method, once, what this call should even do).
the difference is when the target gets decided: at link time, or every time from the object, or by code you wrote.
the picture
you know how dog.sound() and list.size() look like the same kind of thing in source? to the JVM they're different instructions, and so are new Dog(), super.sound(), Math.max(1, 2) and () -> {}.
think of making a phone call. invokestatic is dialing a fixed number: always the same person. invokespecial is calling a specific person's direct line, skipping the switchboard. invokevirtual is calling "the manager on duty": the switchboard looks up who that is today (the object's class) and connects you. invokeinterface is calling "whoever handles billing" in a building where every office organizes departments differently. invokedynamic is calling a number that, the first time, asks you to describe what you want, then wires up a permanent line for next time.
guess the instruction
every call below was compiled with jdk 25 and read back with javap. pick what you think javac emitted.
pick the instruction javac emits for that call.
arrayList.size() vs list.size(), and private methods.the rule javac follows, in order:
| the call | instruction | target decided |
|---|---|---|
| a static method (in a class or interface) | invokestatic | at link time |
a constructor, or super.m() | invokespecial | at link time, exact method |
| an instance method, through a class type | invokevirtual | per call, from the receiver's class |
| an instance method, through an interface type | invokeinterface | per call, from the receiver's class |
a lambda, a method ref, string +, record methods | invokedynamic | once, by a bootstrap method |
notice the type that matters is the declared type of the expression, not the object. list.size() and arrayList.size() can call the very same method on the very same object and still compile to different instructions.
how invokevirtual finds the method
every class has a vtable: an array of pointers to its overridable methods, built when the class is linked. a subclass copies its parent's vtable and replaces the slots it overrides. so sound() sits at the same index in Animal's table and in Dog's, and a call only needs that index plus the receiver's class.
Animal.sound:()Ljava/lang/String;
javac wrote invokevirtual Animal.sound, by name. the first time it runs, the JVM resolves that name once: sound() lives in vtable slot 5. it caches the number.
new Animal() and new Dog(), then step. the call site and slot 5 never change; only the class pointer in the object does. blue dots are pointers. vtable sizes from jcmd <pid> VM.classes -verbose: Object has 5 entries, Animal and Dog have 6.a new final or private method doesn't add a vtable slot (nothing can override it), and Animal's vtable really is 6 long because it adds exactly one overridable method.
invokeinterface can't use a fixed index: Dog might implement ten interfaces, and Comparable.compareTo can't be slot 5 in every class that implements it. so each class also has an itable (one block of method pointers per interface it implements), and the call first finds the right block, then indexes into it. a bit more work, which is why interface calls used to have a reputation for being slow.
in practice, neither table lookup happens much in hot code. the JIT records which classes actually show up at each call site. if it's always Dog, C2 compiles the call as "is it a Dog? then run Dog.sound() inlined right here", no table at all. that's covered in JIT optimizations.
the private method surprise
before java 11, javac compiled a call to a private method with invokespecial (exact target, no dispatch). and when an inner class called its outer class's private method, javac had to generate a hidden static bridge, access$000, because to the JVM they were two unrelated classes.
java 11 added nestmates (JEP 181): classes compiled from the same source file record each other in NestHost / NestMembers attributes, and the JVM lets them touch each other's private members directly. along with it, javac switched private calls to invokevirtual. same source, two compilers:
int self();
1: invokespecial #1 // Method secret:()I
static int access$000(Outer);
1: invokespecial #1 // Method secret:()I
int peek(); // in Outer$Inner
4: invokestatic #13 // Method Outer.access$000:(LOuter;)Iint self();
1: invokevirtual #7 // Method secret:()I
int peek(); // in Outer$Inner
4: invokevirtual #19 // Method Outer.secret:()I
NestMembers:
Outer$Innerthe bridge method is gone. but does invokevirtual on a private method mean it's virtual now?
class Parent {
private String who() { return "parent"; }
String call() { return who(); }
}
class Child extends Parent {
String who() { return "child"; }
}
void main() {
IO.println(new Child().call());
}class Parent {
private String who() { return "parent"; }
String call() { return who(); }
}
class Child extends Parent {
String who() { return "child"; }
}
void main() {
IO.println(new Child().call());
}parent
invokedynamic: a call site you program
the first four instructions name their target in the constant pool. invokedynamic names a bootstrap method instead. the first time the instruction runs, the JVM calls the bootstrap, which returns a CallSite holding the real target. the instruction is then permanently linked to it.
hover a stage.
that's how lambdas exist without one anonymous class file per lambda, how string concatenation picks its strategy at runtime, how records get toString/equals/hashCode (ObjectMethods.bootstrap), and how pattern switch dispatches. the instruction was added in java 7 for dynamic languages on the JVM (JRuby, Groovy) and java itself started using it in 8. the full story is in invokedynamic.
invokevirtual on a final method, and signature polymorphism+
calling a final method still compiles to invokevirtual (javac doesn't treat final specially, so recompiling a library that removes final doesn't break callers). HotSpot notices at link time that the method can't be overridden and binds the call directly, no vtable involved.
MethodHandle.invokeExact is stranger. its declared signature is (Object...)Object, but javac compiles each call with the descriptor of the actual arguments at that call site, like (Ljava/lang/String;I)V. these are "signature polymorphic" methods, and they're the reason method handles can be as fast as direct calls.
- five instructions: static, special, virtual, interface, dynamic.
- the declared type picks the instruction. the object's class picks the method.
- virtual calls are an index into the receiver's vtable. no searching.
- private calls are invokevirtual since java 11, and still never overridden.
- invokedynamic asks a bootstrap method once, then calls the answer forever.