On this page
Bytecode Basics
Java source code compiles to bytecode — platform-independent instructions executed by the JVM. Understanding bytecode helps with debugging, performance analysis, and framework internals.
Compilation Pipeline
Hello.java → javac → Hello.class (bytecode) → JVM → native code
javac Hello.java
javap -c Hello # disassemble bytecode
javap -verbose Hello # full class details
Class File Structure
A .class file contains:
| Section | Contents |
|---|---|
| Magic number | 0xCAFEBABE |
| Version | Major/minor Java version |
| Constant pool | Strings, class names, method refs |
| Access flags | public, final, interface, etc. |
| This class / Super class | Class hierarchy |
| Fields | Field descriptors |
| Methods | Method descriptors + bytecode |
| Attributes | Source file, line numbers, etc. |
Sample Disassembly
Source:
public class Add {
public int add(int a, int b) {
return a + b;
}
}
Bytecode (javap -c Add):
public int add(int, int);
Code:
0: iload_1 // load local variable 1 (a) onto stack
1: iload_2 // load local variable 2 (b) onto stack
2: iadd // pop two ints, push sum
3: ireturn // return int from top of stack
Operand Stack Model
The JVM is a stack-based machine. Most instructions operate on an operand stack:
Stack: []
iload_1 → [5]
iload_2 → [5, 3]
iadd → [8]
ireturn → returns 8
Common Bytecode Instructions
| Instruction | Description |
|---|---|
iconst_0..iconst_5 |
Push int constant 0-5 |
bipush, sipush |
Push byte/short constant |
iload, istore |
Load/store int local variable |
aload, astore |
Load/store reference |
getfield, putfield |
Access instance field |
getstatic, putstatic |
Access static field |
invokevirtual |
Call instance method |
invokestatic |
Call static method |
invokeinterface |
Call interface method |
invokedynamic |
Dynamic method call (lambdas) |
new |
Create object |
ifeq, ifne, goto |
Branch instructions |
iadd, isub, imul |
Integer arithmetic |
return, ireturn, areturn |
Return from method |
invokedynamic and Lambdas
Lambdas compile to invokedynamic instead of anonymous inner classes:
Runnable r = () -> System.out.println("Hello");
The bootstrap method links to a factory that generates the functional object at runtime — more efficient than creating a new class per lambda.
Viewing Bytecode with Tools
# Command line
javap -c -p MyClass.class
# With JDK tools
jdeps MyClass.class # module dependencies
jdeprscan --class-path . MyClass # deprecated API usage
IDE plugins (IntelliJ “Show Bytecode”) and tools like Bytecode Viewer provide GUI access.
Practical Uses
- Debugging — Understand what the JVM actually executes
- Performance — Identify autoboxing, unnecessary object creation
- Security — Audit what code actually runs
- Framework internals — Spring, Hibernate, and Mockito rely heavily on bytecode manipulation (CGLIB, ASM, Javassist)
Best Practices
- Use
javapto inspect unexpected behavior or performance issues - Understand that lambdas use
invokedynamic— not anonymous inner classes - Leave bytecode manipulation to established libraries (ASM, ByteBuddy)
- Match bytecode knowledge with source-level understanding — don’t optimize prematurely