JVM internals
Class loading, runtime memory areas, bytecode and the JIT.
From source to running code
javac compiles .java to platform-independent bytecode (.class). The JVM loads it, verifies it, and runs it, first in the interpreter and then as native code once the JIT compiles the hot paths.
Class loading
Three steps: load (find the bytes), link (verify → prepare static fields with defaults → resolve symbolic references), initialise (run static initialisers, once, on first active use).
Loaders delegate parent-first: Bootstrap (core java.*) → Platform → Application (your classpath). A class is identified by its name plus its loader, so the same class loaded twice by different loaders is two different types.
Runtime memory areas
| Area | Shared? | Holds |
|---|---|---|
| Heap | all threads | every object and array; managed by the GC |
| Stack | one per thread | a frame per method call: local variables, operands, return address |
| Metaspace | all threads | class metadata (native memory since Java 8, replaced PermGen) |
| PC register | one per thread | the current bytecode instruction |
| Native method stack | one per thread | frames for native (JNI) calls |
StackOverflowError = a thread’s stack is full (usually deep recursion). OutOfMemoryError = the heap or metaspace can’t grow.
JIT compilation
Methods start interpreted. The JVM counts calls and loops; hot code is compiled by C1 (fast, light optimisation), then C2 (slower, aggressive: inlining, escape analysis, loop unrolling). This tiered compilation is why Java “warms up”.
Interview angles
- Why is Java “write once, run anywhere”? Bytecode + a JVM per platform.
- Where do local variables vs objects live? Primitives and references on the stack; objects on the heap.
- What is escape analysis? If an object never escapes a method, the JIT can skip the heap allocation (scalar replacement).