Fundamentals 15 min read

How Does the JVM Choose the Right Method? Inside Stack Frames and Dynamic Dispatch

This article explains how Java’s JVM uses stack frames, local variable tables, operand stacks, dynamic linking, and method return addresses to manage method calls, detailing the distinction between static and dynamic dispatch, the role of non‑virtual methods, and how the invokevirtual instruction resolves the correct implementation at runtime.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
How Does the JVM Choose the Right Method? Inside Stack Frames and Dynamic Dispatch

Introduction

Polymorphism is a core feature of Java that gives the language its dynamic nature. When a thread executes a method, the JVM must determine which version of the method to run.

Stack Frame

The JVM stores a method's local variable table, operand stack, dynamic linking information, and return address in a stack frame. Each method call pushes a frame onto the stack and pops it when the call returns.

2.1 Local Variable Table

The local variable table consists of slots. According to the Java Virtual Machine Specification, each slot can hold a boolean, byte, char, short, int, float, reference, or returnAddress.

These eight types can be stored in 32 bits or less. In a 64‑bit JVM, slots are padded to keep the layout compatible with a 32‑bit environment.

Long and double values occupy two consecutive slots with high‑order alignment, and the JVM forbids accessing only one of those slots; violations are caught during class‑loading verification.

Class variables receive a default value during the preparation phase and are assigned the programmer‑defined value during initialization. Method variables have no preparation phase; using an uninitialized method variable is illegal.

2.2 Variable Slot Reuse

To save memory, slots can be reused once the program counter moves beyond a variable's scope. Reuse can affect garbage collection because a slot may still hold a reference.

Example without just‑in‑time compilation (JIT):

public static void main(String[] args) {
    {
        byte[] placeholder = new byte[64 * 1024 * 1024];
    }
    System.gc();
}

Example with an additional variable that reuses the slot:

public static void main(String[] args) {
    {
        byte[] placeholder = new byte[64 * 1024 * 1024];
    }
    int a = 0;
    System.gc();
}

In practice, most programs run under JIT compilation, which can optimize away the reference, allowing the placeholder to be reclaimed.

2.3 Operand Stack

The operand stack provides temporary storage for variables during computation and holds intermediate results. It can only be accessed via push and pop operations.

Many JVM implementations overlap parts of consecutive stack frames: the upper frame's operand stack overlaps with the lower frame's local variable table, saving space and allowing direct reuse of values.

2.4 Dynamic Linking

After compilation, class files store symbolic references to fields and methods in the constant pool. Direct references can be resolved at compile time, while others (e.g., overloaded methods) require runtime resolution.

Dynamic linking converts these symbolic references into direct references during execution.

2.5 Method Return Address

A method can exit normally or by throwing an exception. When method A calls method B, A's stack frame stores the program counter as the return address. For exceptional exits, the return address is determined via the exception handler table.

After a method returns, the JVM:

Restores the caller's local variable table and operand stack.

Pushes the return value onto the caller's stack frame.

Advances the program counter to the instruction following the call.

2.6 Additional Information

Different JVM implementations may embed extra information in stack frames for debugging or performance monitoring.

Method Invocation

All method calls are stored in class files as symbolic references. Some calls can only be resolved at class‑loading or runtime, which is the basis of Java’s dynamic extensibility.

3.1 Invocation Instructions

The JVM defines five bytecode instructions for method invocation:

invokestatic – calls static methods.

invokespecial – calls constructors ( ), private methods, and superclass methods.

invokevirtual – calls virtual methods.

invokeinterface – calls interface methods, resolved at runtime.

invokedynamic – resolves the target method dynamically at runtime.

invokestatic and invokespecial can be resolved during the class‑loading resolution phase. The five methods that meet this criterion are static methods, private methods, instance constructors, superclass methods, and final methods (invoked via invokevirtual). These are called “non‑virtual methods”; the rest are “virtual methods”.

3.2 Resolution

If a method’s version can be determined during the class‑loading resolution phase, the call is said to be resolved. Static and private methods satisfy this condition.

Non‑virtual methods (invokestatic, invokespecial, and final methods) are resolved at load time, while virtual methods are resolved at runtime.

3.3 Dispatch

Static dispatch occurs at compile time, using the static type to select the method version. Dynamic dispatch occurs at runtime, using the actual object type.

3.3.1 Static Dispatch Example

public class StaticDispatch {
    static abstract class Human {}
    static class Man extends Human {}
    static class Woman extends Human {}
    public void sayHello(Human guy) { System.out.println("hello,guy!"); }
    public void sayHello(Man guy) { System.out.println("hello,gentleman!"); }
    public void sayHello(Woman guy) { System.out.println("hello,lady!"); }
    public static void main(String[] args) {
        StaticDispatch sd = new StaticDispatch();
        Human man = new Man();
        Human woman = new Woman();
        sd.sayHello(man);
        sd.sayHello(woman);
    }
}

Both calls print hello,guy! because the compiler selects the method based on the static type Human, not the actual type.

Method Selection Order

The compiler chooses the most specific applicable overload, but the choice may not be unique.

public class Overload {
    public static void sayHello(Object arg){ System.out.println("hello Object"); }
    public static void sayHello(int arg) { System.out.println("hello int"); }
    public static void sayHello(long arg) { System.out.println("hello long"); }
    public static void sayHello(Character arg){ System.out.println("hello Character"); }
    public static void sayHello(char arg) { System.out.println("hello char"); }
    public static void sayHello(char... arg) { System.out.println("hello char..."); }
    public static void sayHello(Serializable arg) { System.out.println("hello Serializable"); }
    public static void main(String[] args) { sayHello('a'); }
}

The output is hello char. If the char overload is removed, the compiler would match int following the conversion chain char → int → long → float → double.

3.3.2 Dynamic Dispatch Example

public class DynamicDispatch {
    static abstract class Human { public void speak(){ System.out.println("I'm human"); } }
    static class Man extends Human { @Override public void speak(){ System.out.println("I'm man"); } }
    static class Woman extends Human { @Override public void speak(){ System.out.println("I'm woman"); } }
    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        man.speak();
        woman.speak();
        man = new Woman();
        man.speak();
    }
}

Output:

I'm man
I'm woman
I'm woman

Here the exact method implementation cannot be determined at compile time; the JVM resolves it at runtime using the invokevirtual instruction.

invokevirtual Execution Process

Identify the actual class C of the object on top of the operand stack.

Search C for a method matching the name and descriptor; if found, perform access checks and obtain a direct reference.

If not found, repeat the search up the inheritance hierarchy.

If still not found, throw java.lang.AbstractMethodError.

Note: Java has virtual methods but no “virtual fields”. If a subclass defines a field with the same name as a superclass field, the subclass field hides the superclass field.

Comparison of Dispatch Types

Dispatch Type

Principle

Phase

Use Case

Static Dispatch

Determines method version based on static type

Compiler

Overloading

Dynamic Dispatch

Determines method version based on actual type

Runtime

Overriding

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaJVMstack-frameMethod DispatchDynamic Dispatch
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.