Start Learning
Javaneer
Back to stage
Stage 4·Advanced Java

JVM Internals

Class loading, runtime memory areas, JIT compilation, and garbage collection.

24 min readAdvanced
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.

Class loading is like a librarian fetching books

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.

AreaHoldsLifespan
StackMethod calls & local variablesPer thread, short-lived
HeapAll objects (created with new)Shared, garbage-collected
MetaspaceClass metadataLong-lived
Stack vs. heap: a desk and a warehouse

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 it

The 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.

The GC is like an automatic cleaning crew

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 GC

Modern 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

  1. You write .java, compile to bytecode .class.
  2. The class loader loads and verifies classes on demand.
  3. Bytecode runs - interpreted at first, then JIT-compiled when hot.
  4. Objects live on the heap; local variables and calls live on the stack.
  5. 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.