JVM Architecture in Java
The class loader, the runtime data areas and the execution engine, and how a class file becomes running machine code.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
The three parts
.class files
|
v
+------------------+ +---------------------------+ +------------------+
| Class Loader |->| Runtime Data Areas |<-| Execution Engine |
| load, link, init | | heap, stacks, metaspace | | interpreter, JIT |
+------------------+ +---------------------------+ +------------------+
|
v
native instructions1. The class loader subsystem
It finds a class file, verifies it, prepares it and initialises it. This is covered in detail in the note on class loading; the summary is that classes arrive lazily, on first use, and never before.
2. Runtime data areas
| Area | Shared | Holds |
|---|---|---|
| Heap | Yes | Every object and array |
| Metaspace | Yes | Class metadata, method bytecode, static fields |
| JVM stack | One per thread | Frames: locals, operands, the return address |
| Program counter | One per thread | The address of the current instruction |
| Native method stack | One per thread | Frames for native code |
| Code cache | Yes | Machine code produced by the JIT |
public void process() {
int count = 5; // on this thread stack
String label = "total"; // reference on the stack, object on the heap
List<String> items = new ArrayList<>(); // reference on the stack, object on the heap
}The primitive value and the reference live in the frame; the object always lives on the heap. When the method returns, the frame is discarded and the object survives only if something else still refers to it.
3. The execution engine
The interpreter
Reads bytecode one instruction at a time and performs it. Starting is immediate, but each instruction is re-decoded on every pass.
The just in time compiler
The JVM counts how often each method and loop runs. Once a threshold is crossed, the method is compiled to native code and future calls use the compiled version. This is why a Java process becomes faster after it has been running for a while.
call 1..n interpreted, counters increasing
threshold C1 compiles quickly, with basic optimisation
still hot C2 compiles slowly, with aggressive optimisation
assumption
broken deoptimise back to the interpreter, then recompileTiered compilation
Modern JVMs use both compilers: C1 gives quick improvement, and the code that stays hot is recompiled by C2 with far more optimisation. The JVM also gathers profile data while interpreting, which lets it make assumptions a static compiler cannot, such as that a call site only ever sees one implementation.
The stack in detail
public int calculate() {
return add(2, 3);
}
private int add(int a, int b) {
return a + b;
}Thread stack (grows downwards)
+-----------------------+
| calculate() frame | locals: this
+-----------------------+
| add() frame | locals: this, a=2, b=3
+-----------------------+ operand stack used for the additionEach frame holds a local variable array, an operand stack for intermediate values, and a reference to the constant pool of its class. Exceeding the stack depth, usually through unbounded recursion, throws StackOverflowError.
Bytecode
public int add(int a, int b) {
return a + b;
}iload_1 push a
iload_2 push b
iadd pop two, add, push the result
ireturn return the top of the operand stackThe JVM is a stack machine: operands are pushed and popped rather than held in named registers. Inspect any class with javap -c ClassName.
Useful command line tools
| Tool | Purpose |
|---|---|
javap -c | Disassemble bytecode |
jps | List running JVMs |
jcmd | A general diagnostic command interface |
jstack | Thread dump |
jmap | Heap summary and histogram |
jstat | Live garbage collection statistics |
jps -l
jcmd <pid> VM.flags
jcmd <pid> GC.heap_info
jstack <pid> > threads.txtCommon startup flags
java -Xms512m -Xmx2g initial and maximum heap
-Xss1m stack size per thread
-XX:+UseG1GC choose the collector
-XX:+HeapDumpOnOutOfMemoryError
-XX:MaxMetaspaceSize=256m
-jar application.jarThe JVM is a specification
HotSpot is the common implementation, but the JVM is a document that anything may implement. That is also why languages other than Java can target it: they simply emit valid class files.
Common misconceptions
- Java is slow because it is interpreted. Hot code is compiled to native instructions at runtime.
- The JVM is the same as the JDK. The JVM executes bytecode; the JDK is the whole development kit.
- Objects can live on the stack. Only frames and their contents do; escape analysis may avoid an allocation, but that is an optimisation, not a rule you can rely on.
- More heap is always better. A larger heap can mean longer pauses when it is collected.
Practice
- Compile a small class and read its bytecode with
javap -c. - Explain which memory area holds a local
int, aStringreference and theStringobject. - Why does a long running server often become faster after several minutes?
- Use
jpsandjcmdto inspect a running Java process. - What triggers
StackOverflowError, and which area is exhausted?
Conclusion
The JVM loads classes, keeps objects on a shared heap and frames on per thread stacks, and executes bytecode by interpreting it and then compiling the hot parts. Nearly every performance and memory question comes back to that structure.