JVM Internals
Class loading, runtime memory areas, JIT compilation, and garbage collection.
On this page
You've been running code on the JVM since Stage 0. Now let's open the hood. Understanding how the JVM loads classes, manages memory, optimizes code, and cleans up garbage will make you a sharper, more confident Java developer.
Class loading
When your program uses a class for the first time, the JVM's class loader
finds the .class file, loads its bytecode, verifies it's safe, and prepares it
for use. This happens lazily - classes load on demand, not all at once.
You don't haul every book home at once. When you need one, the librarian finds it, checks it's not damaged (verification), and hands it over. The JVM loads each class the first time it's needed, checks the bytecode is valid, then keeps it ready.
Runtime memory areas
The JVM divides memory into regions. The two you must know are the stack and the heap.
| Area | Holds | Lifespan |
|---|---|---|
| Stack | Method calls & local variables | Per thread, short-lived |
| Heap | All objects (created with new) | Shared, garbage-collected |
| Metaspace | Class metadata | Long-lived |
The stack is your desk: small, fast, and tidy. Each task (method call) gets a slip of space for its notes (local variables), cleared the instant the task ends. The heap is a huge warehouse where all your actual products (objects) live. Variables on the desk hold references - slips pointing to boxes in the warehouse.
void example() {
int x = 5; // 'x' lives on the STACK
Dog dog = new Dog(); // the Dog object lives on the HEAP;
// 'dog' (a reference to it) lives on the STACK
} // when the method ends, the stack frame is gone;
// the Dog object stays until garbage collection reclaims itThe JIT compiler: getting fast
Bytecode starts out interpreted (read and executed instruction by instruction). But the JVM watches which code runs often ("hot" code) and the Just-In-Time (JIT) compiler translates it into optimized native machine code on the fly.
Why Java gets faster as it runs
Because the JIT optimizes hot paths using real runtime information, long-running Java programs can rival - sometimes beat - languages compiled ahead of time. It's also why a benchmark's first run is slower than later runs: the JIT hasn't warmed up yet.
Garbage collection
In many languages you must free memory by hand. In Java, the garbage collector (GC) does it automatically: it finds objects no longer reachable by your program and reclaims their memory.
Imagine a crew that quietly walks the warehouse, spotting boxes nobody can reach anymore (no labels point to them) and clearing them out - all while you keep working. You never manually take out the trash; the collector handles it. That's automatic memory management.
An object becomes eligible for collection when nothing references it:
Dog dog = new Dog(); // object is reachable
dog = null; // now nothing points to it → eligible for GCModern JVMs offer several GC algorithms (G1, ZGC, Shenandoah) tuned for different goals - high throughput vs. ultra-low pause times. For most apps, the default (G1) is excellent.
You still have to avoid leaks
Automatic GC doesn't mean you can't leak memory. If you keep references to objects you no longer need - for example, adding to a collection and never removing - the GC can't reclaim them. "Memory leak" in Java usually means "accidentally holding on too long."
Putting it together: the lifecycle
- You write
.java, compile to bytecode.class. - The class loader loads and verifies classes on demand.
- Bytecode runs - interpreted at first, then JIT-compiled when hot.
- Objects live on the heap; local variables and calls live on the stack.
- The garbage collector reclaims unreachable objects automatically.
Quick check
Where do objects created with 'new' live, and where do local variables holding references live?
Key takeaways
- The class loader finds, verifies, and loads class bytecode lazily, on first use.
- The stack holds method calls and local variables (short-lived); the heap holds all objects.
- Variables often hold references (on the stack) that point to objects (on the heap).
- The JIT compiler turns frequently-run bytecode into optimized native code, so Java speeds up as it runs.
- The garbage collector automatically reclaims unreachable objects; leaks happen when you hold references too long.
One more advanced topic rounds out Stage 4: organizing large codebases with the Java module system.