Bytecode Basics in Java

Naveen AnamaneniUpdated · 9 min read

Bytecode is the instruction set of the Java Virtual Machine: the compact, platform-independent code that javac writes into .class files and the JVM executes. Each instruction is one byte (the opcode, which gives bytecode its name), sometimes followed by a few bytes of operands. The JVM is a stack machine: instead of working with CPU registers, instructions push values onto an operand stack and pop them off. In this lesson, you will learn how a method's frame is organized, the main families of instructions, how a loop looks in bytecode, and the five instructions that call methods: invokestatic, invokevirtual, invokeinterface, invokespecial and invokedynamic.

You don't write bytecode by hand, but reading it explains how Java features really work and what the JIT compiler starts from. "Javap Tool in Java" showed how to print it.

The Frame: Local Variables and the Operand Stack

Every method call gets a frame on the thread's stack (see "Stack vs Heap Memory in Java"). A frame has two working areas:

Load pushes a local onto the stack; store pops it back
AreaWhat it holds
Local variable arrayNumbered slots for this (slot 0 in instance methods), then the parameters, then local variables. long and double take two slots.
Operand stackA working stack. Instructions pop their inputs from it and push their results onto it.

The compiler records the sizes both need (stack=2, locals=3 in javap -v), so the JVM can allocate the frame in one step.

Instruction Families

Most instruction names begin with a letter for the type they work on:

PrefixType
iint (also used for boolean, byte, char and short)
llong
ffloat
ddouble
aA reference (an object or array)
FamilyExamplesWhat they do
Constantsiconst_0 ... iconst_5, bipush 35, sipush 1000, ldc #7, aconst_nullPush a constant
Loadiload_1, aload_0, iload 7Push a local variable onto the stack
Storeistore_1, astore_2Pop the stack top into a local variable
Arithmeticiadd, isub, imul, idiv, irem, ineg, iinc 2, 1Pop operands, push the result (iinc changes a local directly)
Conversioni2l, i2d, l2d, d2i, i2bConvert between primitive types
Objectsnew, getfield, putfield, getstatic, putstatic, checkcast, instanceofCreate objects, access fields, cast
Arraysnewarray, arraylength, iaload, iastoreCreate and access arrays
Stackdup, pop, swapRearrange the stack
Controlif_icmpgt, ifeq, ifnull, goto, tableswitch, lookupswitchJump to another position
Callsinvokestatic, invokevirtual, invokeinterface, invokespecial, invokedynamicCall methods
Returnireturn, areturn, returnReturn a value, or nothing

Short forms such as iload_1 and iconst_3 exist for the most common cases, so they take a single byte.

Example: A Loop in Bytecode

Sum.java▶ Try it
public class Sum {
    static int sumTo(int n) {
        int total = 0;
        for (int i = 1; i <= n; i++) {
            total += i;
        }
        return total;
    }
 
    public static void main(String[] args) {
        System.out.println(sumTo(4));
    }
}
Output
10
javap -c Sum (the sumTo method)
  static int sumTo(int);
    Code:
         0: iconst_0
         1: istore_1
         2: iconst_1
         3: istore_2
         4: iload_2
         5: iload_0
         6: if_icmpgt     19
         9: iload_1
        10: iload_2
        11: iadd
        12: istore_1
        13: iinc          2, 1
        16: goto          4
        19: iload_1
        20: ireturn

Explanation: The method is static, so slot 0 is n, slot 1 is total and slot 2 is i.

PositionsBytecodeJava
0 to 1Push 0, store it in slot 1int total = 0;
2 to 3Push 1, store it in slot 2int i = 1;
4 to 6Push i and n; if i > n, jump to 19The loop test i <= n, written as its opposite
9 to 12Push total and i, add them, store in slot 1total += i;
13Add 1 to slot 2 directlyi++
16Jump back to 4Repeat the loop
19 to 20Push total, return itreturn total;

There is no "for" instruction. Every loop and if becomes conditional jumps and goto. The compiler tests the opposite condition (if_icmpgt for <=) so that it jumps out of the loop when the condition fails.

The Five Invoke Instructions

InstructionUsed forHow the target is chosen
invokestaticStatic methodsFixed at link time; no object involved
invokevirtualInstance methods of a classAt run time, from the actual class of the object (overriding)
invokeinterfaceMethods called through an interface typeAt run time, by searching the object's class for the interface method
invokespecialConstructors (<init>), super.method() callsFixed: exactly the named method, with no overriding
invokedynamicLambdas, string concatenation, pattern switches, record methodsA bootstrap method links the call the first time it runs

Let's see an example that uses all five:

Calls.java▶ Try it
import java.util.ArrayList;
import java.util.List;
 
public class Calls {
    private int count;
 
    static int square(int n) {
        return n * n;
    }
 
    private void bump() {
        count++;
    }
 
    public static void main(String[] args) {
        int nine = square(3);
        Calls c = new Calls();
        c.bump();
        List<String> names = new ArrayList<>();
        names.add("Navika");
        String text = names.toString();
        Runnable r = () -> System.out.println(text + " " + nine);
        r.run();
    }
}
Output
[Navika] 9
javap -p -c Calls (the main method)
     0: iconst_3
     1: invokestatic  #13                 // Method square:(I)I
     4: istore_1
     5: new           #8                  // class Calls
     8: dup
     9: invokespecial #17                 // Method "<init>":()V
    12: astore_2
    13: aload_2
    14: invokevirtual #18                 // Method bump:()V
    17: new           #21                 // class java/util/ArrayList
    20: dup
    21: invokespecial #23                 // Method java/util/ArrayList."<init>":()V
    24: astore_3
    25: aload_3
    26: ldc           #24                 // String Navika
    28: invokeinterface #26,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    33: pop
    34: aload_3
    35: invokeinterface #32,  1           // InterfaceMethod java/util/List.toString:()Ljava/lang/String;
    40: astore        4
    42: aload         4
    44: iload_1
    45: invokedynamic #36,  0             // InvokeDynamic #0:run:(Ljava/lang/String;I)Ljava/lang/Runnable;
    50: astore        5
    52: aload         5
    54: invokeinterface #40,  1           // InterfaceMethod java/lang/Runnable.run:()V
    59: return

Explanation:

  • invokestatic (1): square(3). The argument 3 is pushed, the call pops it and pushes the result, which istore_1 saves in nine.
  • new, dup, invokespecial (5 to 9): creating an object takes three steps. new allocates memory and pushes a reference, dup copies it, and invokespecial runs the constructor <init> on one copy, leaving the other to be stored.
  • invokevirtual (14): c.bump(). The object reference is pushed first, like a hidden first argument. (Before Java 11, private methods were called with invokespecial.)
  • invokeinterface (28, 35, 54): names has the type List, an interface, so calls through it use invokeinterface, even for toString(). The pop at 33 throws away add's unused boolean result.
  • invokedynamic (45): the lambda. The two captured variables, text and nine, are pushed, and the call returns a Runnable. The first time it runs, the bootstrap method LambdaMetafactory.metafactory creates the object's class; later runs reuse that link.

Why Invokedynamic Matters

The first four instructions have their behavior fixed by the JVM. invokedynamic lets Java code decide how a call site is linked, through a bootstrap method that returns a method handle (see "Method Handles in Java"). That gives the language room to improve without new bytecode: string concatenation moved to invokedynamic in Java 9 and got faster, and records, pattern-matching switches and lambdas all rely on it.

Common Mistakes

Assuming bytecode is what the CPU runs. It is the input to the interpreter and the JIT, which then optimize it (see "JIT Optimizations in Java").

Judging performance by counting instructions. After inlining and escape analysis, the machine code can look very different.

Forgetting slot 0 is this. In instance methods, the first parameter is in slot 1.

Expecting boolean instructions. boolean, byte, char and short are handled as int on the stack.

Interview Corner

What are the invoke instructions in JVM bytecode?

There are five. invokestatic calls static methods. invokevirtual calls instance methods with dynamic dispatch based on the object's actual class. invokeinterface calls methods through an interface reference. invokespecial calls constructors and super methods exactly, without dispatch. invokedynamic (Java 7) defers linking to a bootstrap method at first execution and is used for lambdas, string concatenation, records and pattern-matching switches.

What does it mean that the JVM is a stack machine?

Its instructions don't name registers. They take operands from an operand stack and push results back: to add two locals, the code loads both onto the stack (iload_1, iload_2), runs iadd, and stores the result (istore_3). Each method call has a frame with a local variable array and an operand stack, whose maximum sizes the compiler records in the class file.

Why are new and the constructor call separate instructions?

new only allocates the object and pushes a reference to uninitialized memory. The constructor is an ordinary method named <init> that must be called with invokespecial. The dup between them keeps a second reference on the stack, because the constructor call consumes one.

Practice

  1. Easy: Compile a method static long twice(long x) { return x * 2; } and find the l-prefixed instructions with javap -c.
  2. Harder: Write a method that calls Object.toString() on a variable declared as Object, one declared as List, and one via super.toString() inside a class. Predict the invoke instruction for each, then check.

Quick check: Bytecode basics

4 questions
1What does iload_1 do?
2Which instruction calls a constructor?
3Which instruction is used for a lambda expression?
4How is a for loop represented in bytecode?

Key Takeaways

Was this tutorial helpful?

Lesson 473 of 482