Skip to content

Lesson 03 · JVM Awareness

Beyond the 1Z0-830 exam

You don't need JVM internals to pass the exam, but a working developer needs enough of a mental model to reason about memory, performance, and the odd OutOfMemoryError or ClassNotFoundException. This is intuition, not tuning — just enough to make good design choices and read a stack trace.

Objectives

After this lesson you will be able to:

  • Distinguish the stack (per-thread frames, locals) from the heap (shared objects).
  • Explain garbage collection at a high level: reachability, roots, generations.
  • Describe class loading and the common errors it produces.
  • Apply a few realistic performance intuitions (and avoid premature optimization).

Stack vs heap

StackHeap
HoldsMethod frames: locals, operand stack, return addressesAll objects and arrays
ScopeOne per threadShared across all threads
LifetimePops when the method returnsUntil unreachable, then GC'd
ErrorsStackOverflowError (deep/infinite recursion)OutOfMemoryError: Java heap space

A local variable of a reference type lives on the stack, but it holds a reference to an object on the heap. Primitives declared as locals live entirely on the stack.

java
void m() {
    int x = 42;                 // x (the int) is on this frame's stack
    String s = new String("hi"); // s (the reference) on the stack; the String object on the heap
}                                // frame pops; the String is now eligible for GC if unreferenced

Gotcha

Deep or unbounded recursion throws StackOverflowError, not OutOfMemoryError. Allocating too many live objects throws OutOfMemoryError: Java heap space. They're different failures with different fixes (bound the recursion vs. reduce/stream the data).

Garbage collection — reachability, not reference counting

The GC reclaims objects that are no longer reachable from a GC root (live thread stacks, static fields, JNI handles). You never call free; you just drop references.

  • Setting a reference to null helps only if it was the last reachable reference to that object.
  • An object referenced from a still-live collection (a cache, a static list) is not collected — that's the usual shape of a Java "memory leak."
  • Most JVMs use generational collection: new objects in a young generation (cheap, frequent collections); long-lived ones get promoted to the old generation. This works because most objects die young.
  • System.gc() is only a hint — don't rely on it; never put it in production code.

SDET note

A test that holds objects in a static collection (or a thread-local that's never cleared) leaks across tests and can blow the heap in a long suite. "It passes alone but the full run OOMs" usually means shared static state — clear it in teardown (Module 14/15).

Class loading

Classes are loaded lazily, on first active use, by a delegating chain of class loaders (bootstrap → platform → application/classpath). Loading does: load the bytecode → link (verify, prepare, resolve) → initialize (run static initializers and static field assignments).

Common errors:

ErrorTypical cause
ClassNotFoundExceptionReflective load (Class.forName) of a class not on the classpath
NoClassDefFoundErrorClass was present at compile time but missing/failed to init at runtime
ExceptionInInitializerErrorA static initializer threw while the class was being initialized

Static initialization runs once, the first time the class is initialized — a handy, thread-safe lazy-singleton mechanism (the "initialization-on-demand holder" idiom).

Performance intuition (without premature optimization)

  • Measure before optimizing. Reach for a profiler/JMH, not a guess; the bottleneck is rarely where you think.
  • Allocation is cheap, but not free. Avoid needless garbage in hot loops (e.g. string + in a tight loop → StringBuilder; reuse buffers). Elsewhere, prefer clarity.
  • Big-O beats micro-tuning. An O(n²) contains-in-a-List loop dwarfs any constant-factor trick; pick the right data structure (a HashSet) first.
  • The JIT compiles hot methods to native code after warm-up — which is exactly why a microbenchmark that ignores warm-up lies. Benchmark with a real harness.

Beyond the exam

This whole lesson is background intuition, not exam material — but it's the difference between "the code compiles" and "the code scales." When reviewing AI-generated code (Module 21), JVM awareness is what flags an accidental O(n²), an unbounded cache, or a System.gc() call.

Key Takeaways

  • Stack = per-thread frames (locals, primitives, references); heap = shared objects. Deep recursion → StackOverflowError; too many live objects → OutOfMemoryError.
  • The GC reclaims unreachable objects from GC roots; null-ing helps only if it was the last reference. Leaks are usually live references in caches/statics.
  • Generational GC exploits "most objects die young"; System.gc() is just a hint.
  • Classes load lazily and initialize once; know ClassNotFoundException vs NoClassDefFoundError vs ExceptionInInitializerError.
  • Measure first; fix Big-O and data structures before micro-optimizing, and account for JIT warm-up when benchmarking.

Lesson Quiz

Lesson Quiz · JVM Awareness0 / 5
  1. Where do a thread's local variables and call frames live?

    • AThe heap
    • BThe stack
    • CThe string pool
    • DThe metaspace
  2. Infinite recursion most likely throws…

    • AOutOfMemoryError
    • BStackOverflowError
    • CClassNotFoundException
    • DNullPointerException
  3. When is an object eligible for garbage collection?

    • AWhen you call free() on it
    • BWhen it is no longer reachable from any GC root
    • CImmediately after the method that created it returns
    • DOnly when you set it to null
  4. A class compiled fine but at runtime you get NoClassDefFoundError. What does that suggest?

    • AA syntax error
    • BThe class was present at compile time but is missing or failed to initialize at runtime
    • CThe JVM is out of heap
    • DA reflection-only problem
  5. Best first step when code is too slow?

    • AReplace all + with StringBuilder everywhere
    • BCall System.gc() more often
    • CMeasure/profile to find the real bottleneck and check algorithmic complexity
    • DAdd more threads

Next: Module 13 · Build, Tooling & Ecosystem. Run this module's code in labs/src/main/java/com/jse21/m12_design/.