Java Advanced · Ay 1
JVM Architecture
Goal: understand how the JVM loads, stores and executes your code. Duration: 120 min
1. Big picture
.java ──javac──▶ .class (bytecode) ──▶ JVM
├── Class Loader subsystem
├── Runtime data areas (memory)
└── Execution engine (interpreter + JIT + GC)
2. Class loading
Classes are loaded lazily: the first time they are used.
| Class loader | Loads |
|---|---|
| Bootstrap | core JDK (java.lang.*) |
| Platform | other JDK modules |
| Application | your classpath (your code, libraries) |
Phases: Loading → Linking (verify, prepare, resolve) → Initialization (static blocks run).
public class Config {
static {
System.out.println("Config class initialized");
}
static final String NAME = "app";
}
The static block runs once, when Config is first used.
Delegation model: a loader first asks its parent. That's why you cannot replace java.lang.String with your own class.
Errors you will meet:
ClassNotFoundException: loading by name failed (e.g. missing JDBC driver).NoClassDefFoundError: class was present at compile time, missing at runtime (dependency conflict).
3. Runtime memory areas
| Area | Per | Contains |
|---|---|---|
| Heap | JVM | all objects and arrays (GC managed) |
| Stack | thread | frames: local variables, method calls |
| Metaspace | JVM | class metadata (replaced PermGen) |
| PC register | thread | current instruction |
| Native stack | thread | native (C) calls |
void transfer() {
int amount = 100; // stack (primitive)
Account acc = new Account("AZ01"); // reference on stack, object on heap
}
- Deep recursion →
StackOverflowError(stack full). - Too many live objects →
OutOfMemoryError: Java heap space.
4. Bytecode
public int add(int a, int b) { return a + b; }
javac Calc.java
javap -c Calc
public int add(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: ireturn
The JVM is a stack machine: it pushes values and applies operations.
5. Execution engine: interpreter + JIT
- Bytecode is first interpreted (slow but starts fast).
- "Hot" methods (called often) are compiled to native code by the JIT compiler (C1 quick, C2 optimizing).
- JIT optimizations: inlining, dead-code elimination, escape analysis, loop unrolling.
That's why Java apps "warm up": performance improves after some time. Never benchmark the first seconds; use JMH for benchmarks.
6. Useful JVM flags
java -Xms512m -Xmx2g -jar app.jar # initial / max heap
java -XX:+PrintFlagsFinal -version | grep HeapSize
java -Xss1m # thread stack size
java -XX:MaxRAMPercentage=75 -jar app.jar # containers (Docker/K8s)
7. Hands-on
- Write a class with a static block and observe when it runs.
- Cause a
StackOverflowErrorwith recursion; change-Xssand compare depth. - Disassemble a small class with
javap -cand explain each instruction. - Print runtime info:
Runtime rt = Runtime.getRuntime();
System.out.printf("Max heap: %d MB, CPUs: %d%n", rt.maxMemory() / 1_048_576, rt.availableProcessors());
8. Interview corner
- Difference between heap and stack?
- What is the JIT compiler?
ClassNotFoundExceptionvsNoClassDefFoundError?- Where are static variables stored? (in the heap, with the class's
Classobject, since Java 8)
9. Homework
Write a one-page explanation (README in your repo) "What happens when I run java -jar app.jar" from class loading to JIT, with a diagram.
Self-check answers
- Heap: shared, objects, GC; stack: per thread, frames, locals, freed automatically when methods return.
- JIT compiles frequently executed bytecode to optimized native code at runtime.
- CNFE: explicit loading by name failed; NCDFE: class existed at compile time but is missing/failed to initialize at runtime.